WPF における XAML とカスタム クラス

.NET で実装された Extensible Application Markup Language (XAML) では、任意の common language runtime (CLR) 言語でカスタムのクラスや構造体を定義して、XAML マークアップを使用してアクセスすることができます。 Windows Presentation Foundation (WPF) で定義されている型とカスタム型は、通常はカスタム型を XAML 名前空間プレフィックスにマップすることで、同じマークアップ ファイルで使用することができます。 ここでは、カスタム クラスを XAML 要素として使用するために満たされている必要がある要件について説明します。

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

  • アプリケーション内のカスタム クラスとアセンブリ内のカスタム クラス
  • XAML 要素としてのカスタム クラスの要件
  • カスタム クラスのプロパティの XAML 属性としての要件
  • カスタム クラスのイベントの XAML イベント ハンドラー属性構文の要件
  • コレクション プロパティの作成
  • XAML コンテンツ プロパティの宣言
  • XAML のシリアル化
  • 関連トピック

アプリケーション内のカスタム クラスとアセンブリ内のカスタム クラス

XAML で使用するカスタム クラスを定義するには 2 つの方法があります。1 つは、分離コードや、プライマリ Windows Presentation Foundation (WPF) アプリケーションを生成するその他のコード内で定義する方法です。もう 1 つは、実行可能ファイルや、クラス ライブラリとして使用される DLL などの別のアセンブリ内のクラスとして定義する方法です。 これらの方法には、それぞれ利点と欠点があります。

  • クラス ライブラリを作成する場合の利点は、それらのカスタム クラスを異なる多くのアプリケーションの間で共有できることです。 また、独立したライブラリを使用することにより、アプリケーションのバージョン管理や、XAML ページのルート要素としての使用を目的としたクラスの作成が簡単になります。

  • カスタム クラスをアプリケーション内で定義する場合の利点は、比較的軽量であることです。メインのアプリケーションの実行可能ファイルとは別のアセンブリを導入する場合に生じる配置やテストの問題を最小限に抑えることができます。 ただし、そのアプリケーションにある他の XAML ページのルート要素と同じアセンブリ内で定義されるクラスを使用できないという、1 つの大きな欠点があります。

  • 同じアセンブリで定義するか別のアセンブリで定義するかに関係なく、カスタム クラスを XAML で要素として使用するためには、CLR 名前空間と XML 名前空間にマッピングする必要があります。 「XAML 名前空間および WPF XAML の名前空間の割り当て」を参照してください。

XAML 要素としてのカスタム クラスの要件

カスタム クラスをオブジェクト要素としてインスタンス化できるようにするには、次の要件が満たされている必要があります。

  • カスタム クラスがパブリックであり、既定の (パラメーターなしの) パブリック コンストラクターがサポートされている (構造体に関する説明は次のセクションを参照してください)。

  • カスタム クラスが入れ子になっていない。 入れ子になったクラスや、そのクラスの一般的な CLR で使用される構文の "ドット" は、添付プロパティなどの他の WPF または XAML の機能に干渉します。

オブジェクト要素構文を使用できるようになると、オブジェクト定義でそのオブジェクトを値の型とする他のパブリック プロパティでプロパティ要素構文を使用できるようにもなります。 これは、オブジェクト要素としてインスタンス化できるようになったオブジェクトは、そのプロパティのプロパティ要素値として設定できるためです。

構造体

カスタム型として定義する構造体は常に WPF 内の XAML に構築できます。これは、CLR コンパイラにより、すべてのプロパティ値を既定値に初期化する構造体の既定のコンストラクターが明示的に作成されるためです。 既定のコンストラクターの動作やある構造体におけるオブジェクト要素の使用が望ましくない場合もあります。 その理由は、構造体は値と関数を概念的に一体として入れることを意図しており、このため含まれた値が排他的な解釈を持つ可能性があり、その結果プロパティがまったく設定できない場合が生じるためです。 このような構造体の WPF の例は GridLength です。 一般的にこのような構造体には、異なる解釈や構造体の値のモードを作成する文字列の規則を使って値を属性形式で表示できるようにする型コンバーターを実装します。 この構造体では、コード構築が既定以外のコンストラクターによって行われる場合にも、同じ動作が見られると考えられます。

カスタム クラスのプロパティの XAML 属性としての要件

プロパティでは、値の型 (プリミティブなど) を参照するか、既定のコンストラクターまたは XAML プロセッサからアクセスできる専用の型コンバーターを持つ型のクラスを使用する必要があります。 CLR における XAML の実装では、XAML プロセッサはそのようなコンバーターを、言語プリミティブのネイティブ サポートを通じて検索するか、またはバッキング型定義の型やメンバーに対する TypeConverterAttribute のアプリケーションを通じて検索します。

そのほか、抽象クラス型やインターフェイスを参照することもできます。 抽象クラスやインターフェイスの場合は、そのインターフェイスを実装する実用クラスのインスタンスや、その抽象クラスを派生する型のインスタンスによって、XAML 解析時にプロパティ値が設定されることが想定されます。

プロパティは抽象クラスで宣言できますが、その抽象クラスから派生する実用クラスでしか設定できません。 これは、クラスのオブジェクト要素を作成するには、クラスにパブリックな既定のコンストラクターが必要であるためです。

型コンバーターによって実現される属性構文

属性として割り当てられた専用の型コンバーターをクラス レベルで用意すると、適用される型変換によって、その型をインスタンス化する必要がある任意のプロパティで属性構文を使用できるようになります。 型コンバーターを用意しても、その型のオブジェクト要素の使用は有効になりません。オブジェクト要素の使用は、その型の既定のコンストラクターが存在する場合にのみ有効です。 したがって、型コンバーターによって実現されるプロパティは、その型自体でオブジェクト要素構文もサポートされていない限り、一般にプロパティ構文では使用できません。 例外として、プロパティ要素に文字列を含める場合には、プロパティ要素構文を指定できます。 この使用方法は、実際には属性構文の使用方法と本質的に変わらず、属性値で空白の堅牢な処理が必要な場合を除いては一般的ではありません。 文字列を受け取るプロパティ要素の使用方法と、それと同等な属性の使用方法を次の例に示します。

<Button>Hallo!
  <Button.Language>
    de-DE
  </Button.Language>
</Button>
<Button Language="de-DE">Hallo!</Button>

XAML で属性構文は使用できるが、オブジェクト要素を含むプロパティ要素構文は使用できないプロパティの一例となるのが、Cursor 型を受け取るさまざまなプロパティです。 Cursor クラスには専用の型コンバーター CursorConverter がありますが、既定のコンストラクターは公開されていません。このため、Cursor プロパティは、実際の Cursor 型は参照型であるにもかかわらず、属性構文でしか設定できません。

プロパティごとの型コンバーター

プロパティ レベルで型コンバーターを宣言することもできます。 これにより、プロパティの型のオブジェクトをインラインでインスタンス化する "ミニ言語" が実現されます。これは、渡される属性の文字列値を ConvertFrom 操作の入力として適切な型に基づいて処理することによって行われます。 通常、これは便利なアクセサーを提供するために行われるものであり、XAML でプロパティを設定できるようにするための唯一の手段として使用されるものではありません。 ただし、使用する既存の CLR 型に既定のコンストラクターも、属性として割り当てられた型コンバーターも用意されていない場合に、属性の型コンバーターを使用することもできます。 たとえば、WPF  APIs に含まれている、CultureInfo 型を受け取るプロパティはその一例です。 この場合、WPF では、フレームワークの以前のバージョンで使用されていた互換性と移行のシナリオを考慮して既存の Microsoft .NET Framework CultureInfo 型を使用していますが、CultureInfo 型では、直接 XAML プロパティの値として使用するために必要なコンストラクターや型レベルの型変換はサポートされていません。

XAML を使用するプロパティを公開するときには常に (コントロールを作成する場合は特に)、そのプロパティを依存関係プロパティで補足することを強くお勧めします。 DependencyProperty サポートを使用することでパフォーマンスを向上できるため、XAML プロセッサの既存の Windows Presentation Foundation (WPF) 実装を使用する場合は、これが特に当てはまります。 依存関係プロパティは、XAML でアクセス可能なプロパティに対して期待されるプロパティ システム機能をそのプロパティのために公開します。 これには、アニメーション、データ バインディング、スタイルのサポートなどの機能が含まれます。 詳細については、「カスタム依存関係プロパティ」および「XAML 読み込みと依存関係プロパティ」を参照してください。

型コンバーターの作成と割り当て

プロパティ型の型変換を提供するためにカスタム TypeConverter 派生クラスの作成が必要になることはあります。 XAML の使用をサポートする型コンバーターの派生および作成の手順や、TypeConverterAttribute の適用方法については、「TypeConverters および XAML」を参照してください。

カスタム クラスのイベントの XAML イベント ハンドラー属性構文の要件

イベントを CLR イベントとして使用できるようにするには、既定のコンストラクターをサポートするクラスか、派生クラスでイベントにアクセスできる抽象クラスで、パブリック イベントとして公開する必要があります。 CLR イベントをルーティング イベントとして便利に使用できるようにするには、明示的な add メソッドと remove メソッドを実装する必要があります。それらのメソッドは、CLR イベント シグネチャのハンドラーを追加および削除し、それらのハンドラーを AddHandler メソッドと RemoveHandler メソッドに転送します。 これらのメソッドは、イベントがアタッチされているインスタンスのルーティング イベント ハンドラー ストアのハンドラーを追加または削除します。

メモメモ

AddHandler を使用して直接ルーティング イベントのハンドラーを登録し、ルーティング イベントを公開する CLR イベントを意図的に定義しないようにすることもできます。この方法を使用すると、そのイベントで XAML 属性構文を使用してハンドラーをアタッチできなくなるほか、多くの場合、結果となるクラスによって提供される型の機能の XAML ビューでも透過性が低下するため、一般に推奨されません。

コレクション プロパティの作成

コレクション型を受け取るプロパティには、コレクションに追加されるオブジェクトを指定できる XAML 構文があります。 この構文には注目すべき機能が 2 つあります。

  • コレクション オブジェクトとなるオブジェクトをオブジェクト要素構文で指定する必要はありません。 コレクション型を受け取るプロパティを XAML で指定するたびに、そのコレクション型の存在が暗黙的に示されます。

  • マークアップにおけるコレクション プロパティの子要素が処理されると、コレクションのメンバーになります。 通常は、コレクションのメンバーにコードでアクセスするには、Add などのリスト/ディクショナリ メソッドを使用するか、インデクサーを使用します。 しかし、XAML 構文ではメソッドやインデクサーはサポートされていません。 コレクションは要素ツリーの構築のためにごく一般的に必要になるため、それらのコレクションを宣言型の XAML で設定するための何らかの方法が必要です。 このため、コレクション プロパティの子要素は、コレクション プロパティ型の値であるコレクションに追加することによって処理されます。

.NET XAML の実装では、WPF XAML プロセッサが次の定義を使用してコレクション プロパティを構成します。 プロパティのプロパティ型で次のいずれかを実装する必要があります。

CLR におけるこれらの型には、それぞれ Add メソッドが含まれます。Add メソッドは、XAML プロセッサによって、オブジェクト グラフを作成するときの基礎になるコレクションに項目を追加するために使用されます。

メモメモ

ジェネリック List インターフェイスおよびジェネリック Dictionary インターフェイス (IList<T> および IDictionary<TKey, TValue>) は、WPF XAML プロセッサによるコレクション検出ではサポートされていません。一方、List<T> クラスおよび Dictionary<TKey, TValue> クラスを基本クラスとして使用することはできます。それぞれ、IListIDictionary を直接実装しているためです。

コレクションを受け取るプロパティを宣言するときには、その型の新しいインスタンスでそのプロパティ値がどのように初期化されるかに注意する必要があります。 プロパティを依存関係プロパティとして実装しない場合は、コレクション型のコンストラクターを呼び出す補足フィールドを使用できます。 プロパティが依存関係プロパティの場合は、そのコレクション プロパティを既定の型コンストラクターの一部として初期化する必要がある可能性があります。 これは、依存関係プロパティはメタデータから既定値を受け取りますが、コレクション プロパティの初期値は、通常、共有された静的コレクションにはしないためです。 包含型のインスタンスごとにコレクション インスタンスが必要です。 詳細については、「カスタム依存関係プロパティ」を参照してください。

コレクション プロパティにカスタム コレクション型を実装することもできます。 暗黙的なコレクション プロパティとして扱われるため、カスタム コレクション型では既定のコンストラクターを用意する必要はありません。既定のコンストラクターがなくても、XAML で暗黙的に使用できます。 ただし、コレクション型の既定のコンストラクターを用意することもできます。 これを実行することで、 既定のコンストラクターを用意しないと、そのコレクションをオブジェクト要素として明示的に宣言できない場合に役立てることができます。 マークアップ スタイルの問題から明示的なコレクションが好まれることもあります。 また、既定のコンストラクターを用意すると、そのコレクション型をプロパティ値として使用する新しいオブジェクトを作成するときの初期化要件が単純化されます。

XAML コンテンツ プロパティの宣言

XAML 言語では、XAML コンテンツ プロパティの概念が定義されています。 オブジェクト構文で使用できる各クラスは、XAML コンテンツ プロパティを 1 つだけ持つことができます。 プロパティをクラスの XAML コンテンツ プロパティとして宣言するには、クラス定義の一部として ContentPropertyAttribute を適用し、 目的の XAML コンテンツ プロパティの名前をその属性の Name として指定します。

コレクション プロパティを XAML コンテンツ プロパティとして指定することもできます。 この結果、そのプロパティの使用方法で、コレクション オブジェクト要素やプロパティ要素のタグを間にはさまずに、オブジェクト要素に 1 つ以上の子要素を持たせることができます。 それらの要素は XAML コンテンツ プロパティの値として扱われ、対応するコレクション インスタンスに追加されます。

既存の WPF XAML コンテンツ プロパティの中には、プロパティ型として Object を使用するものがあります。 この場合、XAML コンテンツ プロパティで、String などのプリミティブ値も、1 つの参照オブジェクト値も受け取ることができます。 このモデルに従う場合は、型の判定や使用できる型の処理をその型で行う必要があります。 Object コンテンツ タイプは一般に、オブジェクト コンテンツを文字列として追加する単純な手段と (既定のプレゼンテーションが適用されます)、既定以外のプレゼンテーションまたは追加データを指定するオブジェクト コンテンツを追加する高度な手段の両方をサポートする場合に使用されます。

XAML のシリアル化

コントロールを作成する場合など、一部のシナリオでは、XAML でインスタンス化できるオブジェクト表現を同等の XAML に再度シリアル化できるようにする必要がある場合もあります。 シリアル化の要件については、ここでは説明しません。 詳細については、「コントロールの作成の概要」および「要素のツリーおよびシリアル化」を参照してください。

参照

概念

XAML の概要 (WPF)

カスタム依存関係プロパティ

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

基本要素の概要

XAML 読み込みと依存関係プロパティ