コントロールの作成の概要

Windows Presentation Foundation (WPF) コントロール モデルの機能拡張により、新しいコントロールを作成する必要性が大幅に削減されます。 ただし、場合によっては、カスタム コントロールを作成する必要があります。 ここでは、カスタム コントロールを作成する必要性を最小限に抑える機能と、Windows Presentation Foundation (WPF) のさまざまなコントロール作成モデルについて説明します。 また、新しいコントロールを作成する方法も示します。

このトピックは、次のセクションで構成されています。

  • 新しいコントロールを作成しないための方法
  • コントロール作成モデル
  • コントロールの作成の基本
  • 関連トピック

新しいコントロールを作成しないための方法

従来、カスタマイズの内容を既存のコントロールから取得する場合は、背景色、境界線の幅、フォントのサイズなど、コントロールの標準プロパティの変更は制限されていました。 このような定義済みのパラメーター以外の部分について、コントロールの外観や動作を拡張するには、新しいコントロールを作成する必要があり、一般的には、既存のコントロールを継承し、コントロールの描画を行うメソッドをオーバーライドしていました。 その方法は現在でも使用できますが、WPF では、リッチ コンテンツ モデル、スタイル、テンプレート、およびトリガーを使用して、既存のコントロールをカスタマイズすることもできます。 これらの機能を使用して、新しいコントロールを作成せずにカスタム操作および一貫した操作を実現する方法には、次のようなものがあります。

  • リッチ コンテンツ。 標準の WPF コントロールの多くは、リッチ コンテンツをサポートします。 たとえば、Button のコンテンツ プロパティは Object 型なので、理論的には、Button 上にはどのようなものでも表示できます。 ボタンにイメージとテキストを表示するには、イメージと TextBlockStackPanel に追加し、その StackPanelContent プロパティに設定します。 コントロールには、WPF のビジュアル要素と任意のデータを表示できるため、新しいコントロールを作成する必要性や、複雑な視覚化をサポートするために既存のコントロールを変更する必要性は少なくなります。 Button のコンテンツ モデルおよび WPF のその他のコンテンツ モデルの詳細については、「WPF のコンテンツ モデル」を参照してください。

  • スタイル。 Style は、コントロールのプロパティを表す値のコレクションです。 スタイルを使用して、新しいコントロールを作成することなく、目的のコントロールの外観や動作の再利用可能な表現を作成できます。 たとえば、すべての TextBlock コントロールに、赤色でフォント サイズ 14 の Arial フォントを設定するとします。 その場合、スタイルをリソースとして作成し、該当する適切なプロパティを設定します。 すると、アプリケーションに追加する各 TextBlock は同じ外観となります。

  • データ テンプレート。 DataTemplate を使用すると、コントロールでのデータの表示方法をカスタマイズできます。 たとえば、DataTemplate を使用して、ListBox でのデータの表示方法を指定できます。 この例については、「データ テンプレートの概要」を参照してください。 DataTemplate では、データの表示形式のカスタマイズのほか、UI 要素を含めることができ、カスタム UI の柔軟性を高めることができます。 たとえば、DataTemplate を使用して、各項目がチェック ボックスを持つ ComboBox を作成できます。

  • コントロール テンプレート。 WPF の多くのコントロールでは、ControlTemplate を使用してコントロールの構造と外観を定義します。これにより、コントロールの外観と機能を分離できます。コントロールの ControlTemplate を再定義すると、外観を大きく変えることができます。 たとえば、信号機のような外観のコントロールが必要だとします。 このコントロールのユーザー インターフェイスと機能は単純です。 このコントロールは 3 つの円で構成され、1 度に点灯するのはそのうちの 1 つだけです。 これを実現する方法をしばらく考えた結果、一度に 1 つだけを選択するという機能は RadioButton で実現できることに気付いたとします。しかし、RadioButton の既定の外観は、信号機とは似ても似付きません。 RadioButton では、外観の定義にコントロール テンプレートを使用しているため、コントロールの要件に合わせて ControlTemplate を簡単に再定義でき、オプション ボタンで信号機を実現できます。

    メモメモ

    RadioButton では DataTemplate を使用できますが、この例は DataTemplate では不十分です。DataTemplate は、コントロールのコンテンツの表示形式を定義するものです。RadioButton の場合、コンテンツとは、RadioButton が選択されているかどうかを示す円の右側に表示される内容です。信号機の例では、オプション ボタンは、"点灯" する円のみであることが必要です。 信号機の場合、外観の要件が、RadioButton の既定の外観とは大きく異なるため、ControlTemplate を再定義する必要があります。一般には、コントロールのコンテンツ (またはデータ) の定義には DataTemplate を使用し、コントロールの構造の定義には ControlTemplate を使用します。

  • トリガー。 Trigger を使用すると、新しいコントロールを作成しなくても、コントロールの外観や動作を動的に変更できます。 たとえば、複数の ListBox コントロールを使用するアプリケーションがあり、各 ListBox の選択項目を赤色の太字で表示するとします。 すぐに思い付くのは、ListBox を継承したクラスを作成し、OnSelectionChanged メソッドをオーバーライドして選択項目の外観を変更するという方法ですが、ListBoxItem のスタイルに、選択項目の外観を変更するトリガーを追加するという方法の方がより適切です。 トリガーを使用すると、プロパティ値の変更や、プロパティ値に基づいた処理を実行できます。 EventTrigger により、イベントが発生したときに処理を実行できます。

スタイル、テンプレート、およびトリガーの詳細については、「スタイルとテンプレート」を参照してください。

一般に、既存のコントロールと同じ機能を持ち外観が異なるコントロールが必要な場合は、このセクションで説明した方法のいずれかを使用して既存のコントロールの外観を変更できないかどうかをまず検討することをお勧めします。

コントロール作成モデル

リッチ コンテンツ モデル、スタイル、テンプレート、およびトリガーを使用すると、新しいコントロールを作成する必要性は大幅に抑えられます。 ただし、場合によっては、新しいコントロールを作成する必要があります。その場合は、WPF の各種のコントロール作成モデルを理解することが重要です。WPF には、コントロールを作成するための一般的なモデルが 3 つあり、各モデルがそれぞれ異なる機能と柔軟性レベルを持ちます。 3 つのモデルの基本クラスは、UserControlControl、および FrameworkElement です。

UserControl からの派生

WPF でコントロールを作成する方法で最も簡単なのは、UserControl を派生する方法です。 UserControl を継承したコントロールを作成するときには、UserControl に既存のコンポーネントを追加し、コンポーネントに名前を付けて、Extensible Application Markup Language (XAML) でイベント ハンドラーを参照します。 次に、指定の要素を参照し、コードでイベント ハンドラーを定義します。 この開発モデルは、WPF でのアプリケーション開発に使用されるモデルと非常によく似ています。

UserControl は、正しく構築されていれば、リッチ コンテンツ、スタイル、およびトリガーの利点を活用できます。 しかし、UserControl を継承したコントロールの場合、そのユーザーは、DataTemplateControlTemplate を使用して外観をカスタマイズできません。 Control クラスまたはその派生クラス (UserControl を除く) を派生して、テンプレートをサポートするカスタム コントロールを作成する必要があります。

UserControl からの派生の利点

次のすべての項目に該当する場合は、UserControl から派生することをお勧めします。

  • アプリケーションの構築と同じ方法でコントロールを構築する必要がある場合。

  • コントロールが既存のコンポーネントだけで構成されている場合。

  • 複雑なカスタマイズをサポートする必要がない場合。

Control からの派生

Control クラスからの派生は、既存の WPF コントロールの多くで使用されるモデルです。 Control クラスを継承したコントロールを作成するときには、テンプレートを使用してその外観を定義します。 それにより、操作ロジックと視覚的表現とが分離されます。 また、イベントではなくコマンドとバインディングを使用し、ControlTemplate の要素の参照をできる限り避けることによって、UI とロジックを分離できます。コントロールの UI とロジックが適切に分離されていると、コントロールのユーザーがコントロールの ControlTemplate を再定義して、その外観をカスタマイズできます。カスタム Control の作成は UserControl の作成ほど単純ではありませんが、カスタム Control では高い柔軟性を得ることができます。

Control からの派生の利点

次のいずれかの項目に該当する場合は、UserControl クラスを使用するのではなく、Control から派生することをお勧めします。

  • コントロールの外観を、ControlTemplate によりカスタマイズ可能にする必要がある場合。

  • コントロールでさまざまなテーマをサポートする必要がある場合。

FrameworkElement からの派生

UserControl または Control から派生するコントロールは、既存の要素の構成に依存します。 多くの状況では、それで問題ありません。FrameworkElement を継承するどのオブジェクトも ControlTemplate に含めることができるためです。 しかし、場合によっては、単純な要素コンポジションでは、コントロールの外観で求められる機能を実現できないことがあります。 このような状況では、FrameworkElement に基づいてコンポーネントを作成するのが適切な選択です。

FrameworkElement に基づくコンポーネントを構築するには、ダイレクト レンダリングとカスタム要素コンポジションの 2 つの標準的な方法があります。 ダイレクト レンダリングでは、FrameworkElementOnRender メソッドをオーバーライドし、コンポーネントのビジュアルを明示的に定義する DrawingContext 操作を提供します。 これは、Image および Border で使用される方法です。 カスタム要素コンポジションでは、Visual 型のオブジェクトを使用してコンポーネントの外観を構成します。 例については、「DrawingVisual オブジェクトの使用」を参照してください。 たとえば、カスタム要素コンポジションを使用する WPF のコントロールとして、Track があります。 また、同じコントロールでダイレクト レンダリングとカスタム要素コンポジションの両方を使用することもできます。

FrameworkElement からの派生の利点

次のいずれかの項目に該当する場合は、FrameworkElement から派生することをお勧めします。

  • コントロールの外観に関して、単純な要素コンポジションで提供されるよりも厳密な制御を必要とする場合。

  • 独自のレンダリング ロジックを定義してコントロールの外観を定義する必要がある場合。

  • UserControl および Control を使用する方法では実現できない新しい方法で既存の要素を構成する必要がある場合。

コントロールの作成の基本

既に述べたように、WPF の最も強力な機能の 1 つは、コントロールの基本的なプロパティの設定だけでなく、外観と動作を変更でき、しかもカスタム コントロールを作成する必要がないということです。スタイル、データ バインディング、およびトリガーの各機能は、WPF プロパティ システムおよび WPF イベント システムによって実現されています。以降のセクションでは、カスタム コントロールの作成に使用されたモデルに関係なく、これらの機能を使用できるようにする方法について説明します。カスタム コントロールのユーザーは、これらの機能を WPF に付属のコントロールと同じように使用できます。 

依存関係プロパティの使用

プロパティが依存関係プロパティの場合、以下が可能です。

  • スタイルでプロパティを設定する。

  • プロパティをデータ ソースにバインドする。

  • プロパティの値として動的リソースを使用する。

  • プロパティをアニメーション化する。

コントロールのプロパティでこれらの機能のいずれかをサポートすることが必要な場合は、依存関係プロパティとして実装する必要があります。 次に示す例では、次の処理を行うことによって、Value という名前の依存関係プロパティを定義します。

  • ValueProperty という名前の DependencyProperty 識別子を public static readonly フィールドとして定義します。

  • DependencyProperty.Register を呼び出してプロパティ システムにプロパティ名を登録し、次の項目を指定します。

    • プロパティの名前。

    • プロパティの型。

    • プロパティを所有する型。

    • プロパティのメタデータ。 メタデータには、プロパティの既定値、CoerceValueCallback、および PropertyChangedCallback が含まれています。

  • 依存関係プロパティの登録に使用したのと同じ Value という名前で CLR ラッパー プロパティを定義し、プロパティの get アクセサーおよび set アクセサーを実装します。 この get アクセサーおよび set アクセサーは、それぞれ GetValueSetValue を呼び出すだけです。 依存関係プロパティのアクセサーには、その他のロジックを含めないことをお勧めします。クライアントおよび WPF は、アクセサーを省略して、GetValue および SetValue を直接呼び出すことがあるためです。 たとえば、プロパティがデータ ソースにバインドされている場合、プロパティの set アクセサーは呼び出されません。 get アクセサーおよび set アクセサーに他のロジックを追加するのではなく、ValidateValueCallbackCoerceValueCallback、および PropertyChangedCallback の各デリゲートを使用して、値が変更したときの対応やチェックを行います。 これらのコールバックの詳細については、「依存関係プロパティのコールバックと検証」を参照してください。

  • CoerceValueCallback に対応する、CoerceValue という名前のメソッドを定義します。 CoerceValue では、Value が MinValue 以上 MaxValue 以下であることを確認します。

  • PropertyChangedCallback に対応する、OnValueChanged という名前のメソッドを定義します。 OnValueChanged では、RoutedPropertyChangedEventArgs<T> オブジェクトを作成し、ValueChanged ルーティング イベントを発生させる準備をします。 ルーティング イベントについては、次のセクションで説明します。

        ''' <summary>
        ''' Identifies the Value dependency property.
        ''' </summary>
        Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Decimal), GetType(NumericUpDown), New FrameworkPropertyMetadata(MinValue, New PropertyChangedCallback(AddressOf OnValueChanged), New CoerceValueCallback(AddressOf CoerceValue)))

        ''' <summary>
        ''' Gets or sets the value assigned to the control.
        ''' </summary>
        Public Property Value() As Decimal
            Get
                Return CDec(GetValue(ValueProperty))
            End Get
            Set(ByVal value As Decimal)
                SetValue(ValueProperty, value)
            End Set
        End Property

        Private Shared Overloads Function CoerceValue(ByVal element As DependencyObject, ByVal value As Object) As Object
            Dim newValue As Decimal = CDec(value)
            Dim control As NumericUpDown = CType(element, NumericUpDown)

            newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue))

            Return newValue
        End Function

        Private Shared Sub OnValueChanged(ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
            Dim control As NumericUpDown = CType(obj, NumericUpDown)

            Dim e As New RoutedPropertyChangedEventArgs(Of Decimal)(CDec(args.OldValue), CDec(args.NewValue), ValueChangedEvent)
            control.OnValueChanged(e)
        End Sub
/// <summary>
/// Identifies the Value dependency property.
/// </summary>
public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(
        "Value", typeof(decimal), typeof(NumericUpDown),
        new FrameworkPropertyMetadata(MinValue, new PropertyChangedCallback(OnValueChanged),
                                      new CoerceValueCallback(CoerceValue)));

/// <summary>
/// Gets or sets the value assigned to the control.
/// </summary>
public decimal Value
{          
    get { return (decimal)GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
}

private static object CoerceValue(DependencyObject element, object value)
{
    decimal newValue = (decimal)value;
    NumericUpDown control = (NumericUpDown)element;

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue));

    return newValue;
}

private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    NumericUpDown control = (NumericUpDown)obj;         

    RoutedPropertyChangedEventArgs<decimal> e = new RoutedPropertyChangedEventArgs<decimal>(
        (decimal)args.OldValue, (decimal)args.NewValue, ValueChangedEvent);
    control.OnValueChanged(e);
}

詳細については、「カスタム依存関係プロパティ」を参照してください。

ルーティング イベントの使用

CLR プロパティの概念を追加の機能で拡張する依存関係プロパティと同様に、ルーティング イベントは、標準の CLR イベントの概念を拡張します。 新しい WPF コントロールを作成する場合は、イベントをルーティング イベントとして実装することをお勧めします。ルーティング イベントは以下の機能をサポートしているためです。

  • 複数のコントロールの親でイベントを処理できます。 イベントがバブル イベントの場合、要素ツリーの単一の親がイベントをサブスクライブできます。 これにより、アプリケーション開発者は、複数のコントロールのイベントに 1 つのハンドラーで対応できます。 たとえば、ListBox の各項目に含まれるコントロール (DataTemplate に含まれているため) の場合、アプリケーション開発者は、コントロールのイベントに対応するイベント ハンドラーを ListBox で定義できます。 いずれかのコントロールでイベントが発生すると、そのイベント ハンドラーが呼び出されます。

  • ルーティング イベントは EventSetter で使用できます。これにより、アプリケーション開発者は、イベントのハンドラーをスタイル内で指定できます。

  • ルーティング イベントは EventTrigger で使用できます。XAML によるプロパティのアニメーション化に有用です。 詳細については、「アニメーションの概要」を参照してください。

次に示す例では、次の処理を行うことによって、ルーティング イベントを定義します。

  • ValueChangedEvent という名前の RoutedEvent 識別子を public static readonly フィールドとして定義します。

  • EventManager.RegisterRoutedEvent メソッドを呼び出して、ルーティング イベントを登録します。 この例では、RegisterRoutedEvent を呼び出すときに、次の情報を指定しています。

    • イベントの名前が ValueChanged であること。

    • ルーティング方法が Bubble であること。ソース (イベントを発生させたオブジェクト) のイベント ハンドラーがまず呼び出された後、直近の親要素のイベント ハンドラー以降、ソースの親要素のイベント ハンドラーが順に呼び出されていくルーティング方法です。

    • イベント ハンドラーの型が RoutedPropertyChangedEventHandler<T> で、Decimal 型で構築されていること。

    • イベントを所有する型が NumericUpDown であること。

  • ValueChanged という名前のパブリック イベントを宣言し、イベント アクセサー宣言を含めます。 この例では、add アクセサー宣言で AddHandler を、remove アクセサー宣言で RemoveHandler を、それぞれ呼び出して、WPF イベント サービスを使用します。

  • ValueChanged イベントを発生させるプロテクト仮想メソッド OnValueChanged を作成します。

        ''' <summary>
        ''' Identifies the ValueChanged routed event.
        ''' </summary>
        Public Shared ReadOnly ValueChangedEvent As RoutedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, GetType(RoutedPropertyChangedEventHandler(Of Decimal)), GetType(NumericUpDown))

        ''' <summary>
        ''' Occurs when the Value property changes.
        ''' </summary>
        Public Custom Event ValueChanged As RoutedPropertyChangedEventHandler(Of Decimal)
            AddHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
                MyBase.AddHandler(ValueChangedEvent, value)
            End AddHandler
            RemoveHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
                MyBase.RemoveHandler(ValueChangedEvent, value)
            End RemoveHandler
            RaiseEvent(ByVal sender As System.Object, ByVal e As RoutedPropertyChangedEventArgs(Of Decimal))
            End RaiseEvent
        End Event

        ''' <summary>
        ''' Raises the ValueChanged event.
        ''' </summary>
        ''' <param name="args">Arguments associated with the ValueChanged event.</param>
        Protected Overridable Sub OnValueChanged(ByVal args As RoutedPropertyChangedEventArgs(Of Decimal))
            MyBase.RaiseEvent(args)
        End Sub
/// <summary>
/// Identifies the ValueChanged routed event.
/// </summary>
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
    "ValueChanged", RoutingStrategy.Bubble, 
    typeof(RoutedPropertyChangedEventHandler<decimal>), typeof(NumericUpDown));

/// <summary>
/// Occurs when the Value property changes.
/// </summary>
public event RoutedPropertyChangedEventHandler<decimal> ValueChanged
{
    add { AddHandler(ValueChangedEvent, value); }
    remove { RemoveHandler(ValueChangedEvent, value); }
}

/// <summary>
/// Raises the ValueChanged event.
/// </summary>
/// <param name="args">Arguments associated with the ValueChanged event.</param>
protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs<decimal> args)
{
    RaiseEvent(args);
}

詳細については、「ルーティング イベントの概要」および「方法 : カスタム ルーティング イベントを作成する」を参照してください。

バインディングの使用

コントロールの UI とロジックとを分離するには、データ バインディングを使用する方法もあります。 これは、ControlTemplate を使用してコントロールの外観を定義する場合に、特に重要になります。 データ バインディングを使用すると、コードから UI の特定の部分を参照する必要がなくなる場合があります。 コードが ControlTemplate 内の要素を参照する場合、ControlTemplate が変更されたときには、参照先の要素を新しい ControlTemplate に含める必要があります。このため、ControlTemplate 内の要素の参照を避けることをお勧めします。

次の例では、NumericUpDown コントロールの TextBlock を更新し、名前を割り当てて、コード内で名前を使用してテキスト ボックスを参照しています。

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
  <TextBlock Name="valueText" Width="60" TextAlignment="Right" Padding="5"/>
</Border>
        Private Sub UpdateTextBlock()
            valueText.Text = Value.ToString()
        End Sub
private void UpdateTextBlock()
{
    valueText.Text = Value.ToString();
}

次の例では、バインディングを使用して同じことを実現しています。

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">

    <!--Bind the TextBlock to the Value property-->
    <TextBlock 
        Width="60" TextAlignment="Right" Padding="5"
        Text="{Binding RelativeSource={RelativeSource FindAncestor, 
                       AncestorType={x:Type local:NumericUpDown}}, 
                       Path=Value}"/>

</Border>

データ バインディングの詳細については、「データ バインディングの概要」を参照してください。

デザイナーに対応したデザイン

WPF Designer for Visual Studio でカスタム WPF コントロールのサポート (たとえば [プロパティ] ウィンドウでのプロパティ編集) を利用するには、以下のガイドラインに従います。 WPF デザイナー向けの開発の詳細については、「WPF デザイナー」を参照してください。

依存関係プロパティ

「依存関係プロパティの使用」で述べたように、CLR の get アクセサーおよび set アクセサーを実装します。 デザイナーは、ラッパーを使用して依存関係プロパティの存在を検出する場合がありますが、WPF およびコントロールのクライアントと同様に、プロパティを取得または設定するときに必ずしもアクセサーを呼び出す必要はありません。

添付プロパティ

以下のガイドラインに従って、カスタム コントロールに添付プロパティを実装する必要があります。

  • PropertyNameProperty という形式の public static readonly DependencyProperty を、RegisterAttached メソッドを使用して作成します。 RegisterAttached に渡すプロパティ名は PropertyName に一致する必要があります。

  • SetPropertyName および GetPropertyName という名前の public static CLR メソッドのペアを実装します。 いずれのメソッドも、DependencyProperty の派生クラスを最初の引数で受け取ります。 SetPropertyName メソッドでは、プロパティの登録データ型と同じ型の引数も受け取ります。 GetPropertyName メソッドでは、同じ型の値を返す必要があります。 SetPropertyName メソッドがない場合、プロパティは読み取り専用としてマークされます。

  • SetPropertyName および GetPropertyName は、対象の依存関係オブジェクトの GetValue メソッドおよび SetValue メソッドのそれぞれに直接転送する必要があります。 デザイナーが添付プロパティにアクセスするときには、メソッド ラッパー経由で呼び出す場合と、対象の依存関係オブジェクトを直接呼び出す場合の両方があります。

添付プロパティの詳細については、「添付プロパティの概要」を参照してください。

共有リソースの定義と使用

アプリケーションと同じアセンブリにコントロールを含めることも、複数のアプリケーションが使用できる別のアセンブリにコントロールをパッケージ化することもできます。 このトピックで示す情報の大部分は、使用する方法に関係なく適用されます。 ただし、1 つだけ例外があります。 アプリケーションと同じアセンブリにコントロールを含める場合は、App.xaml ファイルにグローバル リソースを追加できます。 コントロールだけを含むアセンブリには Application オブジェクトが関連付けられないため、App.xaml ファイルは使用できません。

アプリケーションがリソースを検索するときには、次に示す順序で 3 つのレベルを検索します。

  1. 要素レベル。

    システムは、リソースを参照する要素から検索を開始し、ルート要素に到達するまで、論理上の親のリソースの検索を続けます。

  2. アプリケーション レベル。

    Application オブジェクトで定義されたリソース。

  3. テーマ レベル。

    テーマ レベルのディクショナリは、Themes というサブフォルダーに格納されています。 Themes フォルダー内のファイルは、テーマに対応しています。 たとえば、Aero.NormalColor.xaml、Luna.NormalColor.xaml、Royale.NormalColor.xaml などのファイルがあります。 また、generic.xaml という名前のファイルが含まれている場合もあります。 システムがテーマ レベルでリソースを検索するときには、最初にテーマ固有のファイル内を検索し、その後で generic.xaml 内を検索します。

アプリケーションとは別のアセンブリ内にコントロールを含めるときには、グローバル リソースを要素レベルまたはテーマ レベルに配置する必要があります。 どちらに配置する場合も、それぞれの利点があります。

要素レベルでのリソース定義

要素レベルで共有リソースを定義するには、カスタムのリソース ディクショナリを作成し、それをコントロールのリソース ディクショナリに結合します。 このメソッドで定義する場合は、リソース ファイルに任意の名前を付けることができます。また、それをコントロールと同じフォルダーに配置できます。 要素レベルのリソースでは、単純な文字列をキーとして使用することもできます。 次の例では、Dictionary1.xaml という名前の、LinearGradientBrush リソース ファイルを作成します。

<ResourceDictionary 
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
  <LinearGradientBrush 
    x:Key="myBrush"  
    StartPoint="0,0" EndPoint="1,1">
    <GradientStop Color="Red" Offset="0.25" />
    <GradientStop Color="Blue" Offset="0.75" />
  </LinearGradientBrush>

</ResourceDictionary>

ディクショナリを定義したら、それをコントロールのリソース ディクショナリにマージする必要があります。 これを行うには、XAML またはコードを使用します。

次の例では、XAML を使用してリソース ディクショナリを結合します。

<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</UserControl.Resources>

この方法の欠点は、参照するたびに ResourceDictionary オブジェクトが作成されることです。 たとえば、ライブラリ内に 10 個のカスタム コントロールがあり、XAML を使用して各コントロール用の共有リソース ライブラリを結合する場合は、同一の ResourceDictionary オブジェクトが 10 個作成されてしまいます。 これを回避するには、コード内でリソースを結合し、結果として作成される ResourceDictionary を返す、静的クラスを作成します。

次の例では、共有の ResourceDictionary を返すクラスを作成します。

internal static class SharedDictionaryManager
{
    internal static ResourceDictionary SharedDictionary
    {
        get
        {
            if (_sharedDictionary == null)
            {
                System.Uri resourceLocater =
                    new System.Uri("/ElementResourcesCustomControlLibrary;component/Dictionary1.xaml", 
                                    System.UriKind.Relative);

                _sharedDictionary = 
                    (ResourceDictionary)Application.LoadComponent(resourceLocater);
            }

            return _sharedDictionary;
        }
    }

    private static ResourceDictionary _sharedDictionary;
}

次の例では、InitilizeComponent を呼び出す前に、共有リソースをコントロールのコンストラクター内でカスタム コントロールのリソースと結合します。 SharedDictionaryManager.SharedDictionary は静的プロパティであるため、ResourceDictionary は 1 回だけ作成されます。 また、リソース ディクショナリが InitializeComponent の呼び出し前に結合されるため、コントロールはその XAML ファイルでリソースを使用できます。

public NumericUpDown()
{
    this.Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary);
    InitializeComponent();

}

テーマ レベルでのリソース定義

WPF では、さまざまな Windows テーマ用にリソースを作成できます。 コントロールの作成者は、特定のテーマ用のリソースを定義して、使用するテーマに応じてコントロールの外観を変更できます。 たとえば、Windows クラシック テーマ (Windows 2000 の既定のテーマ) の Button の外観は、Windows Luna テーマ (Windows XP の既定のテーマ) の Button とは異なります。これは、Button が各テーマ用に異なる ControlTemplate を使用するためです。

テーマ固有のリソースは、固有のファイル名でリソース ディクショナリに保持されます。 これらのファイルは、コントロールが格納されているフォルダーのサブフォルダーである Themes フォルダー内に配置する必要があります。 次の表は、リソース ディクショナリ ファイルと、各ファイルに関連付けられているテーマを示しています。

リソース ディクショナリ ファイル名

Windows テーマ

Classic.xaml

Windows XP のクラシックな Windows 9x/2000 の外観

Luna.NormalColor.xaml

Windows XP の既定の青のテーマ

Luna.Homestead.xaml

Windows XP のオリーブのテーマ

Luna.Metallic.xaml

Windows XP のシルバーのテーマ

Royale.NormalColor.xaml

Windows XP Media Center Edition の既定のテーマ

Aero.NormalColor.xaml

Windows Vista の既定のテーマ

すべてのテーマのリソースを定義する必要はありません。 特定のテーマについてリソースが定義されていない場合、コントロールはリソースの Classic.xaml をチェックします。 現在のテーマに対応するファイルまたは Classic.xaml にリソースが定義されていない場合、コントロールは汎用のリソースを使用します。汎用のリソースは、generic.xaml という名前のリソース ディクショナリ ファイルにあります。 generic.xaml ファイルは、テーマ固有のリソース ディクショナリ ファイルと同じフォルダー内にあります。 generic.xaml は特定の Windows テーマには対応していませんが、テーマ レベルのディクショナリです。

テーマおよび UI オートメーションがサポートされた NumericUpDown カスタム コントロールのサンプルには、NumericUpDown コントロール用の 2 つのリソース ディクショナリが含まれています。1 つは generic.xaml に、もう 1 つは Luna.NormalColor.xaml にあります。 アプリケーションを実行し、Windows XP のシルバーのテーマと別のテーマとを切り替えて、2 つのコントロール テンプレートの違いを確認できます。 Windows Vista を使用している場合は、Luna.NormalColor.xaml を Aero.NormalColor.xaml という名前に変えて、Windows クラシック テーマと Windows Vista の既定のテーマの切り替えなど、2 つのテーマの切り替えを行うことができます。

テーマ固有のリソース ディクショナリ ファイルに ControlTemplate を置くときには、次の例に示すように、コントロール用の静的コンストラクターを作成し、OverrideMetadata(Type, PropertyMetadata) メソッドを DefaultStyleKey に対して呼び出す必要があります。

        Shared Sub New()
            DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
        End Sub
static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
               new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}

テーマ リソース用のキーの定義と参照

要素レベルでリソースを定義するときには、文字列をキーとして割り当て、その文字列を使用してそのリソースにアクセスできます。 テーマ レベルでリソースを定義するときには、キーとして ComponentResourceKey を使用する必要があります。 次の例では、generic.xaml でリソースを定義します。

<LinearGradientBrush 
     x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:Painter}, 
                                  ResourceId=MyEllipseBrush}"  
                                  StartPoint="0,0" EndPoint="1,0">
    <GradientStop Color="Blue" Offset="0" />
    <GradientStop Color="Red" Offset="0.5" />
    <GradientStop Color="Green" Offset="1"/>
</LinearGradientBrush>

次の例では、キーとして ComponentResourceKey を指定して、リソースを参照します。

<RepeatButton 
    Grid.Column="1" Grid.Row="0"
    Background="{StaticResource {ComponentResourceKey 
                        TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                        ResourceId=ButtonBrush}}">
    Up
</RepeatButton>
<RepeatButton 
    Grid.Column="1" Grid.Row="1"
    Background="{StaticResource {ComponentResourceKey 
                    TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                    ResourceId=ButtonBrush}}">
    Down
 </RepeatButton>

テーマ リソースの場所の指定

コントロールのリソースを見つけるには、アセンブリにコントロール固有のリソースが含まれていることを、ホスト アプリケーションが認識する必要があります。 これを可能にするには、コントロールが含まれているアセンブリに ThemeInfoAttribute を追加します。 ThemeInfoAttribute には、汎用リソースの場所を指定する GenericDictionaryLocation プロパティと、テーマ固有のリソースの場所を指定する ThemeDictionaryLocation プロパティがあります。

次の例では、GenericDictionaryLocation プロパティと ThemeDictionaryLocation プロパティを SourceAssembly に設定して、汎用リソースとテーマ固有のリソースがコントロールと同じアセンブリに置かれていることを示します。

<Assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly)>
[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, 
           ResourceDictionaryLocation.SourceAssembly)]

参照

概念

WPF におけるパッケージの URI

その他の技術情報

WPF デザイナー

コントロールのカスタマイズ