WPF 名前スコープ
更新 : 2007 年 11 月
名前スコープは、概念であると同時に、XAML で定義されたオブジェクトの名前とそれに対応するインスタンスとの関係を格納するプログラミング オブジェクトでもあります。WPF マネージ コードの名前スコープは、XAML アプリケーションのページの読み込み時に作成されます。プログラミング オブジェクトとしての名前スコープは、INameScope インターフェイスによって定義され、実際的なクラス NameScope によって実装されます。
このトピックには次のセクションが含まれています。
- 読み込まれた XAML アプリケーションにおける名前スコープ
- スタイルとテンプレートにおける名前スコープ
- 名前スコープと名前関連 API
- 関連トピック
読み込まれた XAML アプリケーションにおける名前スコープ
名前スコープは、XAML ページを処理するときに、そのルート要素上に作成されます。ページ内で指定した名前は、それぞれ関連する名前スコープに追加されます。共通ルート要素である要素 (Page や Window など) が名前スコープを常に制御します。マークアップのページのルート要素が FrameworkElement や FrameworkContentElement などの要素である場合は、Page が名前スコープを提供できるよう、XAML プロセッサによって Page ルートが暗黙的に追加されます。名前スコープは、XAML で最初に Name 属性や x:Name 属性が定義されていない場合でも作成されます。
1 つの名前スコープ内で同じ名前を 2 回使用しようとすると、例外が発生します。分離コードを持ち、コンパイル済みアプリケーションの一部である XAML の場合、ページに対する生成クラスの作成時に、この例外が発生します。
解析された要素ツリーへの要素の追加
最初の読み込みおよび処理後の要素ツリーになんらかの追加を行う場合、名前スコープを定義するクラスの適切な RegisterName の実装を呼び出す必要があります。そうしないと、追加したオブジェクトを、FindName などのメソッドを使用して名前から参照できません。Name プロパティ (または x:Name 属性) を設定するだけでは、その名前は名前スコープに登録されません。名前スコープを持つ要素ツリーに指定要素を追加しても、その名前は名前スコープに登録されません。名前スコープは入れ子にできますが、一般的に、ルート要素上の名前スコープに名前を登録します。そうすると、同等の XAML ページが読み込まれた場合に作成される名前スコープと位置が対応します。アプリケーション開発者にとって最も一般的なシナリオでは、RegisterName を使用して、現在のルート上の名前スコープに名前を登録します。RegisterName は、アニメーションとして実行されるストーリーボードを検索する重要なシナリオの一部です。詳細については、「ストーリーボードの概要」を参照してください。同じ論理ツリー内にあるルート要素以外の要素で RegisterName を呼び出しても、ルート要素で RegisterName を呼び出した場合と同様、名前はルートに最も近い要素に登録されます。
コードでの名前スコープ
XAML の読み込みによるのではなくプログラムで作成されるアプリケーションの場合、名前スコープをサポートするためには、ルート要素が INameScope を実装しているか、FrameworkElement または FrameworkContentElement 派生クラスである必要があります。
また、XAML プロセッサによって読み込みおよび処理されない要素の場合、既定では、オブジェクトの名前スコープは作成または初期化されません。後で名前を登録する予定の要素に対しては、新しい名前スコープを明示的に作成する必要があります。要素に対して名前スコープを作成するには、静的な SetNameScope メソッドを呼び出します。dependencyObject パラメータとして要素を、value パラメータとして新しい NameScope コンストラクタの呼び出しを指定します。
SetNameScope に対する dependencyObject として指定したオブジェクトが、INameScope 実装、FrameworkElement、FrameworkContentElement のいずれでもない場合、子要素上で RegisterName を呼び出しても無効です。新しい名前スコープを明示的に作成しなかった場合、RegisterName を呼び出すと例外が発生します。
名前スコープ API をコードで使用する例については、「方法 : 名前のスコープを定義する」を参照してください。
スタイルとテンプレートにおける名前スコープ
WPF のスタイルとテンプレートを使用すると、コンテンツを直接再利用および再適用できます。しかし、スタイルとテンプレートには、テンプレート レベルで定義された名前を持つ要素が含まれている可能性があります。同一のテンプレートが、ページ内で複数回使用される可能性があります。このため、スタイルとテンプレートの両方に、適用先のページに依存しない独自の名前スコープを定義します。
次に例を示します。
<Page
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
>
<Page.Resources>
<ControlTemplate x:Key="MyButtonTemplate" TargetType="{x:Type Button}">
<Border BorderBrush="Red" Name="TheBorder" BorderThickness="2">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Page.Resources>
<StackPanel>
<Button Template="{StaticResource MyButtonTemplate}">My first button</Button>
<Button Template="{StaticResource MyButtonTemplate}">My second button</Button>
</StackPanel>
</Page>
ここでは、同一のテンプレートを 2 つの異なるボタンに適用しています。これらのテンプレートがそれぞれ異なる名前スコープを持たなければ、テンプレート内で使用されている TheBorder という名前により、名前の競合が発生することになります。テンプレートの各インスタンスは独自の名前スコープを持つため、この例の場合、インスタンス化された各テンプレートの名前スコープに、名前が必ず 1 つずつ含まれます。
スタイルも独自の名前スコープを持ちます。これは主に、ストーリーボードの各部分に独自の名前が割り当てられるようにするためです。コントロールのカスタマイズ時にテンプレートを再定義した場合も、これらの名前によって、名前の要素を対象とするコントロール固有の動作が可能となります。
名前スコープが複数存在するため、テンプレート内の指定要素の検索が、ページ内のテンプレートなしの要素の検索よりも困難になります。まず、テンプレートが適用されたコントロールの Template プロパティ値を取得して、適用されたテンプレートを判断する必要があります。その後、テンプレート バージョンの FindName を呼び出し、テンプレートが適用されたコントロールを 2 番目のパラメータとして渡します。
コントロールの作成者が生成している規則で、適用されたテンプレートの特定の指定要素を、コントロール自体によって定義される動作の対象とする場合は、コントロール実装コードから GetTemplateChild メソッドを使用できます。GetTemplateChild メソッドはプロテクトされているため、コントロールの作成者のみがアクセスできます。
テンプレート内での作業中に、テンプレートが適用される名前スコープを取得する必要がある場合は、TemplatedParent を取得した後、そこで FindName を呼び出します。テンプレート内で作業するのは、たとえば、適用したテンプレート内の要素からイベントを発生させるようなイベント ハンドラを記述する場合です。
名前スコープと名前関連 API
FrameworkElement には、FindName、RegisterName、UnregisterName の各メソッドがあります。これらのメソッドを呼び出す要素が名前スコープを所有している場合、要素のメソッドは単純に名前スコープのメソッドを呼び出します。それ以外の場合、親要素が名前スコープを所有しているかどうかが調べられ、名前スコープが見つかるまでこの処理が再帰的に続行されます (XAML プロセッサの動作により、ルートには必ず名前スコープが存在します)。FrameworkContentElement での動作もこれに類似していますが、いずれの FrameworkContentElement も名前スコープを所有しない点だけが異なります。FrameworkContentElement にもそれらのメソッドが存在するため、呼び出しは、最終的に FrameworkElement 親要素まで転送されることができます。
新しい名前スコープを既存のオブジェクトに割り当てるには、SetNameScope を使用します。名前スコープをリセットしたり消去したりするために SetNameScope を複数回呼び出すこともできますが、一般的な使用方法ではありません。また、通常、GetNameScope をコードから使用することはありません。
名前スコープの実装
次のクラスは、INameScope を直接実装します。
ResourceDictionary はディクショナリ - ハッシュ テーブルの実装であるため、名前スコープの代わりにキーを使用します。ResourceDictionary で INameScope を実装する唯一の理由は、ユーザー コードに対して例外を発生可能にして、本当の名前スコープと ResourceDictionary によるキーの処理方法との違いを明確にしたり、名前スコープが特に親要素によって ResourceDictionary に適用されないようにしたりできるためです。
FrameworkTemplate および Style は、明示的なインターフェイス定義を通じて INameScope を実装します。明示的に実装すると、INameScope インターフェイスからこれらの名前スコープにアクセスした場合に、名前スコープが従来どおりに動作します。WPF 内部プロセスは、このアクセス方法を使用して名前スコープと通信します。しかし、明示的なインターフェイス定義は、FrameworkTemplate と Style の通常の API サーフェイスには含まれません。これは、FrameworkTemplate や Style では、INameScope のメソッドを直接呼び出す必要がほとんどないためです。
次のクラスは、System.Windows.NameScope ヘルパ クラスを使用し、NameScope 添付プロパティからその名前スコープ実装に接続することにより、独自の名前スコープを定義します。