次の方法で共有


コントロールオーサリングの概要

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

新しいコントロールの作成の代替手段

従来、既存のコントロールからカスタマイズされたエクスペリエンスを得たい場合は、背景色、罫線の幅、フォント サイズなど、コントロールの標準プロパティの変更に限定されていました。 これらの定義済みのパラメーターを超えてコントロールの外観や動作を拡張する場合は、新しいコントロールを作成する必要があります。通常は、既存のコントロールから継承し、コントロールの描画を担当するメソッドをオーバーライドします。 これは依然としてオプションですが、WPF では豊富なコンテンツ モデル、スタイル、テンプレート、トリガーを使用して既存のコントロールをカスタマイズできます。 次の一覧では、これらの機能を使用して、新しいコントロールを作成することなく、カスタムで一貫性のあるエクスペリエンスを作成する方法の例を示します。

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

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

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

  • コントロール テンプレート。 WPF の多くのコントロールでは、 ControlTemplate を使用してコントロールの構造と外観を定義します。コントロールの外観はコントロールの機能から分離されます。 コントロールの ControlTemplateを再定義することで、コントロールの外観を大幅に変更できます。 たとえば、信号機のようなコンポーネントが必要だとします。 このコントロールには、シンプルなユーザー インターフェイスと機能があります。 コントロールは 3 つの円で、一度に 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に追加し、コンポーネントに名前を付け、XAML でイベント ハンドラーを参照します。 その後、名前付き要素を参照し、コードでイベント ハンドラーを定義できます。 この開発モデルは、WPF でのアプリケーション開発に使用されるモデルによく似ています。

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

UserControl から派生する利点

次のすべてが該当する場合は、 UserControl から派生することを検討してください。

  • アプリケーションのビルド方法と同様にコントロールをビルドする必要があります。

  • コントロールは、既存のコンポーネントのみで構成されます。

  • 複雑なカスタマイズをサポートする必要はありません。

コントロールからの派生

Control クラスからの派生は、既存のほとんどの WPF コントロールで使用されるモデルです。 Control クラスから継承するコントロールを作成するときは、テンプレートを使用して外観を定義します。 これにより、運用ロジックを視覚的表現から分離します。 イベントの代わりにコマンドとバインドを使用し、可能な限り ControlTemplate 内の要素を参照しないようにすることで、UI とロジックを確実に切り離すこともできます。 コントロールの UI とロジックが適切に切り離されている場合、コントロールのユーザーはコントロールの ControlTemplate を再定義して外観をカスタマイズできます。 カスタム Control の構築は、 UserControlを構築するほど簡単ではありませんが、カスタム Control は最も柔軟性を提供します。

制御から派生する利点

次のいずれかに該当する場合は、Control クラスを使用する代わりに、UserControlから派生することを検討してください。

  • ControlTemplateを使用してコントロールの外観をカスタマイズできるようにする必要があります。

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

FrameworkElement からの派生

UserControlまたはControlから派生するコントロールは、既存の要素の作成に依存します。 多くのシナリオでは、 FrameworkElement から継承するすべてのオブジェクトを ControlTemplateに含めることができるため、これは許容できるソリューションです。 ただし、コントロールの外観には、単純な要素構成の機能以上のものが必要な場合があります。 これらのシナリオでは、FrameworkElement に基づいてコンポーネントを作成するのが適切な選択です。

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

FrameworkElement から派生する利点

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

  • 単純な要素構成によって提供される機能を超えて、コントロールの外観を正確に制御する必要があります。

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

  • UserControlControlで可能な以上の新しい方法で既存の要素を構成したいと考えています。

コントロール作成の基本

前に説明したように、WPF の最も強力な機能の 1 つは、コントロールの基本的なプロパティを設定して外観と動作を変更する機能を超える機能ですが、カスタム コントロールを作成する必要はありません。 スタイル、データ バインディング、トリガーの機能は、WPF プロパティ システムと WPF イベント システムによって可能になります。 次のセクションでは、カスタム コントロールの作成に使用するモデルに関係なく、WPF に含まれるコントロールの場合と同様に、カスタム コントロールのユーザーがこれらの機能を使用できるように、従う必要があるいくつかのプラクティスについて説明します。

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

プロパティが依存関係プロパティの場合は、次の操作を行うことができます。

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

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

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

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

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

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

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

    • プロパティの名前。

    • プロパティの型。

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

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

  • プロパティのValuegetアクセサーを実装することで、依存関係プロパティの登録に使用されるのと同じ名前である、setという名前の 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 つの親がイベントをサブスクライブできます。 その後、アプリケーション作成者は、1 つのハンドラーを使用して、複数のコントロールのイベントに応答できます。 たとえば、コントロールが ListBox 内の各項目の一部である場合 ( DataTemplateに含まれているため)、アプリケーション開発者は、 ListBoxでコントロールのイベント ハンドラーを定義できます。 いずれかのコントロールでイベントが発生するたびに、イベント ハンドラーが呼び出されます。

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

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

次の例では、次の手順を実行してルーティング イベントを定義します。

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

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

    • イベントの名前は ValueChanged

    • ルーティング戦略は Bubbleです。つまり、ソース (イベントを発生させるオブジェクト) のイベント ハンドラーが最初に呼び出され、次にソースの親要素のイベント ハンドラーが、最も近い親要素のイベント ハンドラーから開始して連続して呼び出されます。

    • イベント ハンドラーの型は RoutedPropertyChangedEventHandler<T>され、 Decimal 型で構築されます。

    • イベントの所有型が NumericUpDown

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

  • OnValueChanged イベントを発生させる、ValueChangedという名前の保護された仮想メソッドを作成します。

/// <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内にある要素を参照しないようにすることをお勧めします。

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

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

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

デザイナー向けのデザイン

WPF Designer for Visual Studio でカスタム WPF コントロールのサポートを受ける (プロパティ ウィンドウでのプロパティ編集など) には、次のガイドラインに従います。 WPF デザイナーの開発の詳細については、「 Visual Studio での XAML のデザイン」を参照してください。

依存関係プロパティ

「依存関係プロパティの使用」で前述したように、CLR getset アクセサーを実装してください。デザイナーはラッパーを使用して依存関係プロパティの存在を検出できますが、WPF やコントロールのクライアントなど、プロパティを取得または設定するときにアクセサーを呼び出す必要はありません。

添付プロパティ

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

  • public メソッドを使用して作成していた staticreadonly フォームのDependencyPropertyPropertyRegisterAttachedがあります。 RegisterAttachedに渡されるプロパティ名は PropertyName と一致する必要があります。

  • public Set という名前のGet CLR メソッドのペアを実装します。 どちらのメソッドも、最初の引数として DependencyProperty から派生したクラスを受け入れる必要があります。 Set PropertyName メソッドは、型がプロパティの登録済みデータ型と一致する引数も受け入れます。 Get PropertyName メソッドは、同じ型の値を返す必要があります。 Set PropertyName メソッドがない場合、プロパティは読み取り専用としてマークされます。

  • Set PropertyNameGetPropertyName は、それぞれターゲット依存関係オブジェクトの GetValue メソッドと SetValue メソッドに直接ルーティングする必要があります。 デザイナーは、メソッド ラッパーを介して呼び出すか、ターゲット依存関係オブジェクトを直接呼び出すことによって、添付プロパティにアクセスできます。

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

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

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

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

  1. 要素レベル。

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

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

    Application オブジェクトによって定義されたリソース。

  3. テーマレベル。

    テーマ レベルのディクショナリは、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 の Olive テーマ
Luna.Metallic.xaml Windows XP の Silver テーマ
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 オートメーションをサポートする C# または Visual Basic NumericUpDown カスタム コントロールのサンプルには、 NumericUpDown コントロール用の 2 つのリソース ディクショナリが含まれています。1 つは generic.xaml にあり、もう 1 つは Luna.NormalColor.xaml です。

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

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

こちらも参照ください