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

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 つだけです。 少し考えた結果、RadioButton を使用すると、一度に 1 つだけ選択する機能を実現できることに気付いたとします。しかし、RadioButton の既定の外観は信号機のライトとは全然似ていません。 RadioButton では、コントロール テンプレートを使用して外観を定義しているため、コントロールの要件に合わせて ControlTemplate を再定義し、オプション ボタンで信号機を実現するのは簡単です。

    注意

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

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

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

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

コントロール作成モデル

リッチ コンテンツ モデル、スタイル、テンプレート、トリガーを使用すると、新しいコントロールを作成する必要性が最小限に抑えられます。 ただし、新しいコントロールを作成する必要がある場合は、WPF のさまざまなコントロール作成モデルを理解することが重要です。 WPF には、コントロールを作成するための一般的なモデルが 3 つあり、各モデルはそれぞれ異なる機能と柔軟性レベルを備えています。 3 つのモデルの基底クラスは、UserControlControlFrameworkElement です。

UserControl からの派生

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

UserControl が正しく構築されていれば、リッチ コンテンツ、スタイル、トリガーの利点を活用できます。 ただし、UserControl を継承したコントロールの場合、そのユーザーは DataTemplate または ControlTemplate を使用して、外観をカスタマイズすることができません。 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 操作を実行します。 これは、ImageBorder で使用される方法です。 カスタム要素コンポジションでは、Visual 型のオブジェクトを使用して、コンポーネントの外観を構成します。 例については、「DrawingVisual オブジェクトの使用」を参照してください。 Track は、カスタム要素コンポジションを使用する WPF のコントロールの一例です。 同じコントロールでダイレクト レンダリングとカスタム要素コンポジションを混在させることもできます。

FrameworkElement からの派生の利点

次のいずれかの項目に該当する場合、FrameworkElement から派生することを検討してください。

  • コントロールの外観について、単純な要素コンポジションが提供する以上の厳密な制御を必要とする場合。

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

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

コントロール作成の基本

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

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

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

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

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

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

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

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

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

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

    • プロパティの名前。

    • プロパティの型。

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

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

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

  • CoerceValueCallback に対応する、CoerceValue という名前のメソッドを定義します。 CoerceValue によって、ValueMinValue 以上で MaxValue 以下になります。

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

/// <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);
}
''' <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

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

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

依存関係プロパティで 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 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);
}
''' <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

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

バインディングの使用

コントロールの 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 void UpdateTextBlock()
{
    valueText.Text = Value.ToString();
}
Private Sub UpdateTextBlock()
    valueText.Text = Value.ToString()
End Sub

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

<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>

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

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

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

依存関係プロパティ

前の「依存関係プロパティの使用」で説明したように、必ず CLR getset アクセサーを実装してください。デザイナーでは、ラッパーを使用して依存関係プロパティの存在を検出できますが、プロパティを取得または設定する際、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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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;
}

次の例では、InitializeComponent を呼び出す前に、共有リソースをコントロールのコンス トラクター内でカスタム コントロールのリソースと結合します。 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 テーマには対応していませんが、テーマ レベルのディクショナリであることに変わりありません。

C# または Visual Basic の、テーマおよび UI オートメーションがサポートされた NumericUpDown カスタム コントロールのサンプルには、NumericUpDown コントロール用の 2 つのリソース ディクショナリが含まれています。1 つは generic.xaml で、もう 1 つは Luna.NormalColor.xaml です。

テーマ固有のリソース ディクショナリ ファイルのいずれかに ControlTemplate を配置する場合、次の例に示すように、コントロール用の静的コンストラクターを作成し、DefaultStyleKeyOverrideMetadata(Type, PropertyMetadata) メソッドを呼び出す必要があります。

static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
               new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
Shared Sub New()
    DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
End Sub
テーマ リソース用のキーの定義と参照

要素レベルでリソースを定義するときに、文字列をキーとして割り当て、その文字列を使用してリソースにアクセスできます。 テーマ レベルでリソースを定義するときは、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)>

関連項目