XAML および C# を使ってカスタム コントロールを構築する

既にご存じかもしれませんが、Windows 8 XAML プラットフォームの最も強力な機能の 1 つは、非常に柔軟にカスタム コントロールを作成できることです。XAML には、機能性が高く、カスタマイズ可能なコントロールを簡単に作成できる、依存関係プロパティやコントロール テンプレートなどの機能があります。

前回の記事「JavaScript 用 Windows ライブラリ (WinJS) を使ってカスタム コントロールを構築する」では、Jordan Matthiesen が HelloWorld カスタム コントロールを作成する手順を説明しました。今回の記事では、同じコントロールを XAML で作成する手順を説明します。再利用可能なカスタム コントロールの作成に必要なテクニックと概念を紹介し、コントロールのスタイルを設定するテンプレートの作成方法を説明します。また、依存関係プロパティなどの概念を紹介し、カスタムの Generic.xaml ファイルを使って既定のコントロール テンプレートを定義する暗黙的なスタイルを作成する方法について説明します。

シンプルな XAML コントロール

まず、Hello World コントロールを構築します。これは、Windows.UI.XAML.Controls.Control から派生したクラスです。Visual Studio で "空のプロジェクト" テンプレートを使って新しいプロジェクトを作成します。このプロジェクトには、「CustomControls」という名前を付けます。新しい項目テンプレートの追加ウィザードを使って、カスタム コントロールを追加します。

Visual Studio には、テンプレート コントロール用の項目テンプレートがあります。プロジェクトを右クリックして、[追加]、[新しい項目] の順に選択します。

Visual Studio には、テンプレート コントロール用の項目テンプレートがあります。プロジェクトを右クリックして、[追加]、[新しい項目] の順に選択します。

テンプレート コントロール項目テンプレートを使うと、カスタム コントロールの土台になるファイルとスケルトン コードが作成されます

[テンプレート コントロール] を選択して、コントロールの名前を「HelloWorld.cs」に

このテンプレートにより、以下のクラスが作成されます。

 public class HelloWorld : Control
{
    public HelloWorld()    {
        this.DefaultStyleKey = typeof(HelloWorld);
    }
}

この短いコード ブロック中に、2 つの非常に重要な詳細情報が指定されています。1 つは、Control から派生した HelloWorld クラスです。2 つ目は DefaultStyleKey です。これを設定して、このクラスでは暗黙的なスタイルが使われることを XAML プラットフォームに示しています。テンプレート コントロール テンプレートでは、Themes というフォルダーも追加され、そのフォルダーに Generic.xaml という新しいファイルが作成されます。このテンプレートの詳細については、プロジェクト テンプレートのページを参照してください。

コントロールの既定のスタイルは、プラットフォームが自動的に読み込む Generic.xaml カスタム ファイルに定義します。このファイルに "暗黙的なスタイル" を定義することで、特定の種類のすべてのコントロールに既定で適用されるスタイルを定義できます。Themes フォルダー内に作成された Generic.xaml ファイルに、以下でハイライトされている XAML を追加しましょう。

 <ResourceDictionary
    xmlns="https://schemas.microsoft.com/winfx/2006/XAML/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/XAML"
    xmlns:local="using:CustomControls">

    <Style TargetType="local:HelloWorld">
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:HelloWorld">
                    <Border
                       Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <TextBlock Text="HelloWorld" 
                               FontFamily="Segoe UI Light"
                               FontSize="36"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style> 
</ResourceDictionary>

この数行の XAML で、HelloWorld コントロールのすべてのインスタンスに既定で適用されるスタイルを定義しています。コントロール テンプレートを定義し、このテンプレートを使って、このコントロールは単に "HelloWorld" というテキストが指定された TextBlock であることを示しています。

今度は、MainPage.xaml ファイルを開き、以下のマークアップを追加します。"local:" 指定子を使って、HelloWorld クラスが存在する名前空間を指定します。"local:" 指定子は、XAML ファイルの先頭に定義します。

 <local:HelloWorld />

アプリを実行すると、コントロールが読み込まれて、"Hello, World!" というテキストが表示されます。

コントロール オプションの定義

構成可能なオプションを追加すると、コントロールの利便性が高まり、再利用もしやすくなります。ここでは、コントロールが点滅するように設定できるオプションを追加してみましょう。

このオプションを追加するには、依存関係プロパティ (DP) をコントロールに追加します。DP の詳細については、「依存関係プロパティの概要」を参照してください。非常に簡単に DP を追加できる Visual Studio スニペットがあります。コンストラクターの下にカーソルを置き、「propdp」と入力して、Tab キーを 2 回押します。Tab キーを押すとスニペット内の各パラメーターを順番に表示して、指定できます。

 public class HelloWorld : Control
{
    public HelloWorld()    {
        this.DefaultStyleKey = typeof(HelloWorld);
    }

    public bool Blink
    {
        get { return (bool)GetValue(BlinkProperty); }
        set { SetValue(BlinkProperty, value); }
    }

    // Using a DependencyProperty enables animation, styling, binding, etc.
    public static readonly DependencyProperty BlinkProperty =
        DependencyProperty.Register(
            "Blink",                  // The name of the DependencyProperty
            typeof(bool),             // The type of the DependencyProperty
            typeof(HelloWorld),       // The type of the owner of the DependencyProperty
            new PropertyMetadata(     // OnBlinkChanged will be called when Blink changes
                false,                // The default value of the DependencyProperty
                new PropertyChangedCallback(OnBlinkChanged)
            )
        );

    private DispatcherTimer __timer = null;
    private DispatcherTimer _timer
    {
        get
        {
            if (__timer == null)
            {
                __timer = new DispatcherTimer();
                __timer.Interval = new TimeSpan(0,0,0,0,500); // 500 ms interval
                __timer.Tick += __timer_Tick;
            }

            return __timer;
        }
    }

    private static void OnBlinkChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e
    )
    {
        var instance = d as HelloWorld;
        if (instance != null)
        {
            if (instance._timer.IsEnabled != instance.Blink)
            {
                if (instance.Blink)
                {
                    instance._timer.Start();
                }
                else
                {
                    instance._timer.Stop();
                }
            }
        }
    }

    private void __timer_Tick(object sender, object e)
    {
        DoBlink();
    }

    private void DoBlink()
    {
        this.Opacity = (this.Opacity + 1) % 2;
    }
}

MainPage.xaml に戻り、この構成可能なオプションをコントロールに追加します。

 <local:HelloWorld Blink="True" />

アプリを実行すると、コントロールが点滅します。

イベントのサポートの追加

コントロールにイベントを追加すると、機能性が高まります。イベントを使うと、何か操作が行われたときにコントロールから割り込み、実行された操作に対応するコードを実行できます。ここでは、コントロールが点滅するたびに、発生するイベントを追加してみましょう。

 public class HelloWorld : Control
{
    
    ...
...
    private void __timer_Tick(object sender, object e)
    {
        DoBlink();
    }

    private void DoBlink()
    {
        this.Opacity = (this.Opacity + 1) % 2;
        OnBlinked();
    }

    public event EventHandler Blinked;

    private void OnBlinked()
    {
        EventHandler eh = Blinked;
        if (eh != null)
        {
            eh(this, new EventArgs());
        }
    }
}

MainPage.xaml に戻り、x:Name プロパティを要素に追加して、コードの後の方でコントロール インスタンスを取得できるようにします。

 <local:HelloWorld x:Name="HelloWorldWithEvents" Blink="True" />

今度は、MainPage.xaml.cs に、イベント リスナー関数デリゲートを追加して、イベントが発生したら、デバッグ出力が書き出されるようにします。

 HelloWorldWithEvents.Blinked += (object sender, EventArgs args) =>
{
    System.Diagnostics.Debug.WriteLine("HelloWorldWithEvents Blinked");
};

このコードを実行すると、500 ミリ秒ごとにメッセージが Visual Studio の出力コンソール ウィンドウに書き出されます。

パブリック メソッドの公開

コントロールを点滅させるプライベート メソッドの DoBlink は作成できたので、今度はこのメソッドをパブリックにして、好きなときにコントロールを点滅できるようにしましょう。それには、DoBlink メソッドの可視性を public に変更するだけです。

 public class HelloWorld : Control
{
    ...

    public void DoBlink()
    {
        this.Opacity = (this.Opacity + 1) % 2;
        OnBlinked();
    }

    ...
}

これで、C# の分離コードにある DoBlink パブリック メソッドを呼び出すことができます。この呼び出し元には、おそらくボタン クリック ハンドラーを使うことになるでしょう。

 private void Button_Click_1(object sender, RoutedEventArgs e)
{
    HelloWorldWithEvents.DoBlink();
}    

コントロールの再利用

1 プロジェクトのみ、または個人使用のみが目的でコントロールを使うのであれば、パッケージ化や配布について気にする必要はあまりありません。しかし、他の開発者と共有するために再利用可能なコントロールを作成するのであれば、方法がいくつかあります。1 つは、他の開発者が各自のコンピューターにインストールし、プロジェクトに追加できる Visual Studio 拡張機能 SDK を作成することです。その手順の詳細については、C# または Visual Basic を使った SDK の作成についての記事を参照してください。

まとめ

ここでは、次のような、コントロールを実装する場合に最も一般的に行われる処理について方法を説明しました。

  1. ページへのコントロールの組み込み
  2. 構成オプションの提供
  3. イベントのディスパッチと応答
  4. パブリック メソッドによる機能の公開

これで、XAML カスタム コントロールの基本をおわかりいただけたと思います。この先は、既存の XAML コントロールに追加する機能を見つけ、スタイルを変更するかコントロールをサブクラスにして、独自の機能を追加します。筆者の経験では、カスタム コントロールの扱いに慣れると、はるかに自信を持って XAML アプリを作成できるようになりました。ぜひ皆さんも実際にカスタム コントロールを試してみて、経験されたことをオンラインでご紹介ください。

この記事がお役に立てていればさいわいです。カスタム コントロールを作成中に不明点が生じたときは、Windows デベロッパー センターにアクセスし、フォーラム (英語) で質問してください。

--Aaron Wroblewski

Windows プログラム マネージャー