コントロールの作成の概要
更新 : 2007 年 11 月
Windows Presentation Foundation (WPF) コントロール モデルの機能拡張により、新しいコントロールを作成する必要性が大幅に削減されます。ただし、場合によっては、カスタム コントロールを作成する必要があります。ここでは、カスタム コントロールを作成する必要性を最小限に抑える機能と、Windows Presentation Foundation (WPF) のさまざまなコントロール作成モデルについて説明します。また、新しいコントロールを作成する方法も示します。
このトピックには次のセクションが含まれています。
- 新しいコントロールを作成しないための方法
- コントロール作成モデル
- コントロールの作成の基本
- UserControl からの継承と ControlTemplate の使用
- 関連トピック
新しいコントロールを作成しないための方法
従来、カスタマイズの内容を既存のコントロールから取得する場合は、背景色、境界線の幅、フォントのサイズなど、コントロールの標準プロパティの変更は制限されていました。このような定義済みのパラメータ以外の部分について、コントロールの外観や動作を拡張するには、新しいコントロールを作成する必要があり、一般的には、既存のコントロールを継承し、コントロールの描画を行うメソッドをオーバーライドしていました。その方法は現在でも使用できますが、WPF では、リッチ コンテンツ モデル、スタイル、テンプレート、およびトリガを使用して、既存のコントロールをカスタマイズすることもできます。これらの機能を使用して、新しいコントロールを作成せずにカスタム操作および一貫した操作を実現する方法には、次のようなものがあります。
**リッチ コンテンツ。**標準の WPF コントロールの多くは、リッチ コンテンツをサポートします。たとえば、Button のコンテンツ プロパティは Object 型なので、理論的には、Button 上にはどのようなものでも表示できます。ボタンにイメージとテキストを表示するには、イメージと TextBlock を StackPanel に追加し、その StackPanel を Content プロパティに設定します。コントロールには、WPF のビジュアル要素と任意のデータを表示できるため、新しいコントロールを作成する必要性や、複雑な視覚化をサポートするために既存のコントロールを変更する必要性は少なくなります。Button およびその他のコントロールのコンテンツ モデルの詳細については、「Control コンテンツ モデルの概要」を参照してください。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 つのモデルの基本クラスは、UserControl、Control、および FrameworkElement です。
UserControl からの派生
WPF でコントロールを作成する方法で最も簡単なのは、UserControl を派生する方法です。UserControl を継承したコントロールを作成するときには、UserControl に既存のコンポーネントを追加し、コンポーネントに名前を付けて、Extensible Application Markup Language (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 つの標準的な方法があります。ダイレクト レンダリングでは、FrameworkElement の OnRender メソッドをオーバーライドし、コンポーネントのビジュアルを明示的に定義する 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 アクセサは、それぞれ GetValue および SetValue のみを呼び出します。依存関係プロパティのアクセサには、その他のロジックを含めないことをお勧めします。クライアントおよび WPF は、アクセサを省略して、GetValue および SetValue を直接呼び出すことがあるためです。たとえば、プロパティがデータ ソースにバインドされている場合、プロパティの set アクセサは呼び出されません。get アクセサおよび set アクセサに他のロジックを追加するのではなく、ValidateValueCallback、CoerceValueCallback、および PropertyChangedCallback の各デリゲートを使用して、値が変更したときの対応やチェックを行います。これらのコールバックの詳細については、「依存関係プロパティのコールバックと検証」を参照してください。
CoerceValueCallback に対応する、CoerceValue というメソッドを定義します。CoerceValue では、Value が MinValue 以上 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);
}
詳細については、「カスタム依存関係プロパティ」を参照してください。
ルーティング イベントの使用
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);
}
詳細については、「ルーティング イベントの概要」および「方法 : カスタム ルーティング イベントを作成する」を参照してください。
バインディングの使用
コントロールの 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();
}
次の例では、バインディングを使用して同じことを実現しています。
<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>
データ バインディングの詳細については、「データ バインディングの概要」を参照してください。
コマンドの定義と使用
イベントを処理して機能を提供する代わりに、コマンドを定義して使用することを検討します。コントロールでイベント ハンドラを使用する場合、そのイベント ハンドラ内で行われるアクションには、アプリケーションからアクセスできません。コントロールにコマンドを実装すると、アプリケーションからその機能にアクセスできるようになります。
次の例は、2 つのボタンのクリック イベントを処理して NumericUpDown コントロールの値を変更するコントロールの一部を示しています。コントロールがイベント ハンドラを使用するため、コントロールが UserControl であるか ControlTemplate を持つ Control であるかに関係なく、UI とロジックが密に結合されます。
<RepeatButton Name="upButton" Click="upButton_Click"
Grid.Column="1" Grid.Row="0">Up</RepeatButton>
<RepeatButton Name="downButton" Click="downButton_Click"
Grid.Column="1" Grid.Row="1">Down</RepeatButton>
private void upButton_Click(object sender, EventArgs e)
{
Value++;
}
private void downButton_Click(object sender, EventArgs e)
{
Value--;
}
次の例では、NumericUpDown コントロールの値を変更する 2 つのコマンドを定義します。
public static RoutedCommand IncreaseCommand
{
get
{
return _increaseCommand;
}
}
public static RoutedCommand DecreaseCommand
{
get
{
return _decreaseCommand;
}
}
private static void InitializeCommands()
{
_increaseCommand = new RoutedCommand("IncreaseCommand", typeof(NumericUpDown));
CommandManager.RegisterClassCommandBinding(typeof(NumericUpDown),
new CommandBinding(_increaseCommand, OnIncreaseCommand));
CommandManager.RegisterClassInputBinding(typeof(NumericUpDown),
new InputBinding(_increaseCommand, new KeyGesture(Key.Up)));
_decreaseCommand = new RoutedCommand("DecreaseCommand", typeof(NumericUpDown));
CommandManager.RegisterClassCommandBinding(typeof(NumericUpDown),
new CommandBinding(_decreaseCommand, OnDecreaseCommand));
CommandManager.RegisterClassInputBinding(typeof(NumericUpDown),
new InputBinding(_decreaseCommand, new KeyGesture(Key.Down)));
}
private static void OnIncreaseCommand(object sender, ExecutedRoutedEventArgs e)
{
NumericUpDown control = sender as NumericUpDown;
if (control != null)
{
control.OnIncrease();
}
}
private static void OnDecreaseCommand(object sender, ExecutedRoutedEventArgs e)
{
NumericUpDown control = sender as NumericUpDown;
if (control != null)
{
control.OnDecrease();
}
}
protected virtual void OnIncrease()
{
Value++;
}
protected virtual void OnDecrease()
{
Value--;
}
private static RoutedCommand _increaseCommand;
private static RoutedCommand _decreaseCommand;
テンプレートの要素は、次の例に示すようにコマンドを参照できます。
<RepeatButton
Command="{x:Static local:NumericUpDown.IncreaseCommand}"
Grid.Column="1" Grid.Row="0">Up</RepeatButton>
<RepeatButton
Command="{x:Static local:NumericUpDown.DecreaseCommand}"
Grid.Column="1" Grid.Row="1">Down</RepeatButton>
これで、アプリケーションからバインディングを参照して、コントロールがイベント ハンドラを使用する場合にはアクセスできない機能にアクセスできるようになりました。コマンドの詳細については、「コマンド実行の概要」を参照してください。
ControlTemplate での要素が必要であることの指定
前のセクションでは、データ バインディングとコマンドを使用してコントロールがその ControlTemplate 内の要素をコードから参照しないようにする方法について説明しました。しかし、要素の参照が避けられない場合もあります。そのような場合は、コントロールに TemplatePartAttribute を適用する必要があります。この属性は、テンプレートの作成者に ControlTemplate 内の要素の型や名前を知らせます。ControlTemplate 内のすべての要素名を TemplatePartAttribute で指定する必要はありません。名前を指定する要素をできるだけ少なくすることをお勧めします。しかし、コード内で要素を参照する場合は、TemplatePartAttribute を使用する必要があります。
ControlTemplate を使用するコントロールの設計の詳細については、「スタイルの設定が可能なコントロールを設計するためのガイドライン」を参照してください。
デザイナに対応したデザイン
Windows Presentation Foundation (WPF) Designer for Visual Studio でカスタム WPF コントロールのサポート (たとえば [プロパティ] ウィンドウでのプロパティ編集) を利用するには、以下のガイドラインに従います。WPF デザイナ向けの開発の詳細については、「WPF デザイナ」を参照してください。
依存関係プロパティ
「依存関係プロパティの使用」で述べたように、CLR の get アクセサおよび set アクセサを実装します。デザイナは、ラッパーを使用して依存関係プロパティの存在を検出する場合がありますが、WPF およびコントロールのクライアントと同様に、プロパティを取得または設定するときに必ずしもアクセサを呼び出す必要はありません。
添付プロパティ
以下のガイドラインに従って、カスタム コントロールに添付プロパティを実装する必要があります。
PropertyNameProperty という形式の public static readonlyDependencyProperty を、RegisterAttached メソッドを使用して作成します。RegisterAttached に渡すプロパティ名は PropertyName に一致する必要があります。
SetPropertyName および GetPropertyName という名前の public static CLR メソッドのペアを実装します。いずれのメソッドも、DependencyProperty の派生クラスを最初の引数で受け取ります。SetPropertyName メソッドでは、プロパティの登録データ型と同じ型の引数も受け取ります。GetPropertyName メソッドでは、同じ型の値を返す必要があります。SetPropertyName メソッドがない場合、プロパティは読み取り専用としてマークされます。
SetPropertyName および GetPropertyName は、対象の依存関係オブジェクトの GetValue メソッドおよび SetValue メソッドのそれぞれに直接転送する必要があります。デザイナが添付プロパティにアクセスするときには、メソッド ラッパー経由で呼び出す場合と、対象の依存関係オブジェクトを直接呼び出す場合の両方があります。
添付プロパティの詳細については、「添付プロパティの概要」を参照してください。
コントロール用の共有リソースの定義と使用
アプリケーションと同じアセンブリにコントロールを含めることも、複数のアプリケーションが使用できる別のアセンブリにコントロールをパッケージ化することもできます。このトピックで示す情報の大部分は、使用する方法に関係なく適用されます。ただし、1 つだけ例外があります。アプリケーションと同じアセンブリにコントロールを含める場合は、app.xaml ファイルにグローバル リソースを追加できます。コントロールだけを含むアセンブリには Application オブジェクトが関連付けられないため、app.xaml ファイルは使用できません。
アプリケーションがリソースを検索するときには、次に示す順序で 3 つのレベルを検索します。
要素レベル : システムは、リソースを参照する要素から検索を開始し、ルート要素に到達するまで、論理上の親のリソースの検索を続けます。
アプリケーション レベル : Application オブジェクトによって定義されるリソース。
テーマ レベル : テーマ レベルのディクショナリは、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 の既定のテーマ |
すべてのテーマのリソースを定義する必要はありません。特定のテーマについてリソースが定義されていない場合、コントロールは汎用のリソースを使用します。汎用のリソースは、テーマ固有のリソース ディクショナリ ファイルと同じフォルダ内にある、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 に対して呼び出す必要があります。
static NumericUpDown()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
テーマ リソース用のキーの定義と参照
要素レベルでリソースを定義するときには、文字列をキーとして割り当て、その文字列を使用してそのリソースにアクセスできます。テーマ レベルでリソースを定義するときには、キーとして ComponentResourceKey を使用する必要があります。次の例では、generic.xaml でリソースを定義します。
次の例では、キーとして ComponentResourceKey を指定して、リソースを参照します。
テーマ リソースの場所の指定
コントロールのリソースを見つけるには、アセンブリにコントロール固有のリソースが含まれていることを、ホスト アプリケーションが認識する必要があります。これを可能にするには、コントロールが含まれているアセンブリに ThemeInfoAttribute を追加します。ThemeInfoAttribute には、汎用リソースの場所を指定する GenericDictionaryLocation プロパティと、テーマ固有のリソースの場所を指定する ThemeDictionaryLocation プロパティがあります。
次の例では、GenericDictionaryLocation プロパティと ThemeDictionaryLocation プロパティを SourceAssembly に設定して、汎用リソースとテーマ固有のリソースがコントロールと同じアセンブリに置かれていることを示します。
[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly,
ResourceDictionaryLocation.SourceAssembly)]
UserControl からの継承と ControlTemplate の使用
いくつかのサンプルは、NumericUpDown コントロールの異なる記述方法とパッケージング方法を示しています。「DependencyProperty と RoutedEvent を持つ NumericUpDown UserControl のサンプル」では、NumericUpDown は UserControl から継承します。「テーマおよび UI オートメーションがサポートされた NumericUpDown カスタム コントロールのサンプル」では、NumericUpDown は Control から継承し、ControlTemplate を使用します。このセクションでは、2 つの方法のいくつかの相違点について簡単に説明します。また、ControlTemplate を使用するコントロールがより拡張性を持つ理由についても説明します。
最初の主要な相違点は、UserControl から継承する NumericUpDown では ControlTemplate を使用せず、Control から直接継承するコントロールではこれを使用するという点です。次の例は、UserControl から継承するコントロールの XAML を示しています。次の例で示すように、XAML は、アプリケーションを作成して Window または Page で開始したときと非常によく似ています。
<!--XAML for NumericUpDown that inherits from UserControl.-->
<UserControl x:Class="MyUserControl.NumericUpDown"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyUserControl">
<Grid Margin="3">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<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>
<RepeatButton Name="upButton" Click="upButton_Click"
Grid.Column="1" Grid.Row="0">Up</RepeatButton>
<RepeatButton Name="downButton" Click="downButton_Click"
Grid.Column="1" Grid.Row="1">Down</RepeatButton>
</Grid>
</UserControl>
次の例は、Control から継承するコントロールの ControlTemplate を示しています。ControlTemplate は UserControl の XAML と似ていますが、構文が少し異なっています。
<!--ControlTemplate for NumericUpDown that inherits from
Control.-->
<Style TargetType="{x:Type local:NumericUpDown}">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NumericUpDown}">
<Grid Margin="3">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border BorderThickness="1" BorderBrush="Gray"
Margin="2" Grid.RowSpan="2"
VerticalAlignment="Center" HorizontalAlignment="Stretch">
<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value}"
Width="60" TextAlignment="Right" Padding="5"/>
</Border>
<RepeatButton Command="{x:Static local:NumericUpDown.IncreaseCommand}"
Grid.Column="1" Grid.Row="0">Up</RepeatButton>
<RepeatButton Command="{x:Static local:NumericUpDown.DecreaseCommand}"
Grid.Column="1" Grid.Row="1">Down</RepeatButton>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
前の 2 つの例の最も大きな相違点は、ControlTemplate を使用する方の外観がカスタマイズ可能であるのに対して、UserControl から継承する方はそうでない点です。NumericUpDown が UserControl から継承される場合、アプリケーション開発者はコントロールの外観を変更する手段を持ちません。実際に、NumericUPDown は ControlTemplate プロパティを持ちますが (UserControl が Control から継承するため)、それを設定しようとすると、実行時に例外が発生します。これに対して、Control から継承する NumericUpDown を使用するアプリケーション開発者は、コントロール用に新しい ControlTemplate を作成できます。たとえば、TextBlock の上下ではなく左右にボタンを配置する ControlTemplate を作成できます。
2 つの方法の違いは、前の例の構文的な相違で明らかです。ControlTemplate を使用するコントロールは、NumericUpDown 用に Style で Template プロパティを設定します。これは、コントロール テンプレートを作成する一般的な方法です。スタイルで Template プロパティを設定することによって、コントロールのすべてのインスタンスがその ControlTemplate を使用するように指定します。アプリケーション開発者は、NumericcUpDown の Template プロパティを変更して、外観をカスタマイズできます。これに対して、UserControl から継承するコントロールの XAML は、NumericUpDown の Content プロパティを設定します (<UserControl.Content> は XAML で暗黙に指定されます)。アプリケーション開発者が Content プロパティを変更できない場合、NumericUpDown は使用できません。
これらのサンプルのもう 1 つの違いは、コントロールが上向き矢印ボタンと下向き矢印ボタンに応答する方法です。UserControl から継承するコントロールは、クリック イベントを処理します。ControlTemplate を使用するコントロールは、コマンドを実装し、その ControlTemplate でコマンドにバインドします。このため、新しい ControlTemplate を NumericUpDown 用に作成するアプリケーション開発者も、コマンドにバインドしてコントロールの機能を保持することができます。ControlTemplate がコマンドにバインドする代わりにクリック イベントを処理した場合、アプリケーション開発者は、新しい ControlTemplate を作成するときにイベント ハンドラを実装する必要があり、NumericUpDown のカプセル化が壊れることになります。
もう 1 つの相違点は、TextBlock の Text プロパティと Value プロパティとの間のバインディングの構文です。UserControl の場合、このバインディングでは RelativeSource が親の NumericUpDown コントロールであり、Value プロパティにバインドされることを指定します。ControlTemplate の場合、RelativeSource はテンプレートが属するコントロールです。2 つの例では同じことを実現しますが、バインディングの構文が異なります。
「テーマおよび UI オートメーションがサポートされた NumericUpDown カスタム コントロールのサンプル」では、NumericUpDown コントロールはアプリケーションとは異なるアセンブリに置かれ、テーマ レベルのリソースを定義および使用します。しかし、「DependencyProperty と RoutedEvent を持つ NumericUpDown UserControl のサンプル」では、NumericUpDown コントロールはアプリケーションと同じアセンブリに置かれ、テーマ レベルのリソースは定義および使用しません。
最後に、「テーマおよび UI オートメーションがサポートされた NumericUpDown カスタム コントロールのサンプル」では、NumericUpDown コントロール用の AutomationPeer を作成する方法を示しています。カスタム コントロール用の UI オートメーション のサポートの詳細については、「WPF カスタム コントロールの UI オートメーション」を参照してください。
参照
概念
Windows Presentation Foundation におけるパッケージの URI