このドキュメントでは、簡単にスタイルを設定してテンプレート可能にするコントロールを設計する際に考慮すべき一連のベスト プラクティスをまとめます。 組み込みの WPF コントロール セットのテーマ コントロール スタイルに取り組んでいる間に、多くの試行錯誤を経て、この一連のベスト プラクティスに取り組んできました。 スタイルの成功は、スタイル自体と同じくらい適切に設計されたオブジェクト モデルの機能であることを学習しました。 このドキュメントの対象ユーザーは、スタイル作成者ではなくコントロール作成者です。
用語
"スタイル設定とテンプレート化" とは、コントロール作成者がコントロールの視覚的な側面をコントロールのスタイルとテンプレートに遅延させる一連のテクノロジを指します。 この一連のテクノロジには、次のものが含まれます。
スタイル (プロパティ セッター、トリガー、ストーリーボードを含む)。
リソース
コントロール テンプレート。
データ テンプレート。
スタイル設定とテンプレートの概要については、「 スタイル設定とテンプレート」を参照してください。
開始する前に: コントロールについて
これらのガイドラインに進む前に、コントロールの一般的な使用方法を理解し、定義しておくことが重要です。 スタイリングは、手に負えない可能性の集合を露わにします。 (多くのアプリケーションで、多くの開発者によって) 広く使用されるように記述されたコントロールは、コントロールの視覚的な外観を大幅に変更するためにスタイルを使用できるという課題に直面しています。 実際、スタイル設定されたコントロールは、コントロール作成者の意図に似ていなくてもかまいません。 スタイル設定によって提供される柔軟性は本質的に無限であるため、一般的な使用方法のアイデアを使用して、意思決定の範囲を指定できます。
コントロールの一般的な使用方法を理解するには、コントロールの価値提案について考えておくことをお勧めします。 あなたのコントロールは、他のコントロールにはないどんな特別な価値を提供しますか? 一般的な使用方法は、特定の視覚的な外観ではなく、コントロールの哲学とその使用方法に関する妥当な一連の期待を意味します。 この理解により、一般的なケースでは、コンポジション モデルとコントロールのスタイルで定義された動作について、いくつかの前提条件を設定できます。 たとえば、 ComboBoxの場合、一般的な使用方法を理解しても、特定の ComboBox が角を丸めたかどうかについての洞察は得られませんが、 ComboBox にはポップアップ ウィンドウが必要であり、開いているかどうかを切り替える何らかの方法が必要であるという事実を把握できます。
一般的なガイドライン
テンプレート コントラクトを厳密に適用しないでください。 コントロールのテンプレート コントラクトは、要素、コマンド、バインド、トリガー、またはコントロールが正常に機能するために必要または予期されるプロパティ設定で構成される場合があります。
可能な限りコントラクトを最小限に抑えます。
設計時 (つまり、デザイン ツールを使用する場合) は、コントロール テンプレートが不完全な状態になるのが一般的であることを想定して設計します。 WPF では "構成" 状態インフラストラクチャが提供されないため、このような状態が有効である可能性があることを想定してコントロールを構築する必要があります。
テンプレート コントラクトの任意の側面に従っていない場合は、例外をスローしないでください。 この方針に従い、子が多すぎるか少なすぎる場合でも、パネルは例外をスローするべきではありません。
周辺機能をテンプレート ヘルパー要素に組み込みます。 各コントロールは、そのコア機能と真の価値提案に焦点を当て、コントロールの一般的な使用方法によって定義する必要があります。 そのためには、テンプレート内のコンポジション要素とヘルパー要素を使用して、周辺機器の動作と視覚化、つまりコントロールのコア機能に寄与しない動作と視覚化を有効にします。 ヘルパー要素は、次の 3 つのカテゴリに分類されます。
スタンドアロン ヘルパー型は、テンプレート内で "匿名" で使用されるパブリックで再利用可能なコントロールまたはプリミティブです。つまり、ヘルパー要素とスタイル付きコントロールの両方が他方を認識しません。 技術的には、任意の要素を匿名型にすることができますが、このコンテキストでは、対象となるシナリオを可能にする特殊な機能をカプセル化する型について説明します。
型ベースの ヘルパー要素は、特殊化された機能をカプセル化する新しい型です。 通常、これらの要素は、一般的なコントロールやプリミティブよりも狭い範囲の機能で設計されています。 スタンドアロン ヘルパー要素とは異なり、型ベースのヘルパー要素は、それらが使用されるコンテキストを認識し、通常は、それらが属するテンプレートを持つコントロールとデータを共有する必要があります。
名前付き ヘルパー要素は、コントロールがテンプレート内で名前で検索することを想定している一般的なコントロールまたはプリミティブです。 これらの要素にはテンプレート内で既知の名前が付けられるため、コントロールは要素を検索してプログラムで操作できます。 任意のテンプレートに指定された名前を持つ要素は 1 つだけ存在できます。
次の表は、現在コントロール スタイルで使用されているヘルパー要素を示しています (この一覧は完全ではありません)。
要素 タイプ 使用者 ContentPresenter 型ベース Button、 CheckBox、 RadioButton、 Frameなど (すべての ContentControl 型) ItemsPresenter 型ベース ListBox、 ComboBox、 Menuなど (すべての ItemsControl 型) ToolBarOverflowPanel 名前を付けられた ToolBar Popup スタンドアロン ComboBox、 ToolBar、 Menu、 ToolTipなど RepeatButton 名前を付けられた Slider、 ScrollBarなど ScrollBar 名前を付けられた ScrollViewer ScrollViewer スタンドアロン ListBox、 ComboBox、 Menu、 Frameなど TabPanel スタンドアロン TabControl TextBox 名前を付けられた ComboBox TickBar 型ベース Slider ヘルパー要素に必要なユーザー指定のバインドまたはプロパティ設定を最小限に抑えます。 ヘルパー要素では、コントロール テンプレート内で適切に機能するために、特定のバインドまたはプロパティ設定が必要な場合が一般的です。 ヘルパー要素とテンプレート化されたコントロールは、可能な限りこれらの設定を確立する必要があります。 プロパティを設定したり、バインディングを確立したりする場合は、ユーザーが設定した値をオーバーライドしないように注意する必要があります。 具体的なベスト プラクティスは次のとおりです。
名前付きヘルパー要素は親によって識別され、親はヘルパー要素に必要な設定を確立する必要があります。
型ベースのヘルパー要素は、必要な設定をそれ自体で直接確立する必要があります。 これを行うには、ヘルパー要素が使用される情報や、
TemplatedParent
(テンプレートのコントロール型)などのコンテキストを取得する必要がある場合があります。 たとえば、ContentPresenterContent
派生型で使用する場合、TemplatedParent
のContent プロパティをContentControl プロパティに自動的にバインドします。この方法でスタンドアロン ヘルパー要素を最適化することはできません。これは、定義上、ヘルパー要素も親も他方について認識していないためです。
テンプレート内の要素にフラグを設定するには、Name プロパティを使用します。 プログラムでアクセスするためにスタイル内の要素を検索する必要があるコントロールは、
Name
プロパティとFindName
パラダイムを使用して行う必要があります。 コントロールは、要素が見つからないときに例外をスローするのではなく、その要素を必要とする機能をサイレントかつ適切に無効にする必要があります。スタイルでコントロールの状態と動作を表現するためのベスト プラクティスを使用します。 コントロールの状態の変化と動作をスタイルで表現するためのベスト プラクティスの順序付けされた一覧を次に示します。 シナリオを有効にするリストの最初の項目を使用する必要があります。
プロパティの結合 例: ComboBox.IsDropDownOpen と ToggleButton.IsCheckedの間のバインド。
トリガーによって発生するプロパティの変化やプロパティのアニメーション。 例: Buttonのホバー状態。
命令。 例: LineUpCommandで / LineDownCommandScrollBarします。
スタンドアロン ヘルパー要素。 例: TabPanelでのTabControl。
型ベースのヘルパー型。 例: ContentPresenterでButtonし、TickBarでSliderします。
名前付きヘルパー型からのバブル イベント。 スタイル要素からバブル イベントをリッスンする場合は、イベントを生成する要素を一意に識別できるようにする必要があります。 例: ThumbでのToolBar。
カスタム
OnRender
動作。 例: ButtonChromeでのButton。
(テンプレート トリガーではなく) スタイル トリガーを慎重に使用します。 テンプレート内の要素のプロパティに影響するトリガーは、テンプレートで宣言する必要があります。 テンプレートを変更してもトリガーを破棄する必要があることがわかっている場合を除き、コントロールのプロパティに影響するトリガー (
TargetName
なし) をスタイルで宣言できます。既存のスタイルパターンと一貫性を保つ。 多くの場合、問題を解決する方法は複数あります。 可能であれば、既存のコントロール のスタイルパターンに注意し、一貫性を保つ。 これは、同じ基本型 ( ContentControl、 ItemsControl、 RangeBaseなど) から派生するコントロールで特に重要です。
プロパティを公開して、一般的なカスタマイズ シナリオを再テンプレート化せずに有効にします。 WPF はプラグ可能/カスタマイズ可能なパーツをサポートしていないため、コントロール ユーザーには、プロパティを直接設定するか、スタイルを使用してプロパティを設定するかの 2 つのカスタマイズ方法のみが残されます。 その点を念頭に置いて、非常に一般的で優先度の高いカスタマイズ シナリオを対象とするプロパティを明らかにすることが適切です。そうでなければ、再テンプレートが必要になるためです。 カスタマイズ シナリオを有効にするタイミングと方法のベスト プラクティスを次に示します。
非常に一般的なカスタマイズは、コントロールのプロパティとして公開し、テンプレートで使用する必要があります。
あまり一般的ではない (まれではない) カスタマイズは、添付プロパティとして公開し、テンプレートで使用する必要があります。
既知ではありますが、まれなカスタマイズでは、再テンプレートが必要になることは許容されます。
テーマに関する考慮事項
テーマ スタイルは、すべてのテーマで一貫したプロパティ セマンティクスを持つよう試みる必要がありますが、保証はしません。 ドキュメントの一部として、コントロールには、コントロールのプロパティ セマンティクス、つまりコントロールのプロパティの "意味" を説明するドキュメントが必要です。 たとえば、ComboBox コントロールは、Background内の ComboBox プロパティの意味を定義する必要があります。 コントロールの既定のスタイルは、すべてのテーマにわたって、そのドキュメントで定義されているセマンティクスに従うことを試みる必要があります。 一方、コントロール ユーザーは、プロパティ セマンティクスがテーマからテーマに変更される可能性があることに注意する必要があります。 特定のテーマに必要な視覚的制約の下で、特定のプロパティが表現できない場合があります。 (たとえば、クラシック テーマには、多くのコントロールに
Thickness
を適用できる単一の罫線がありません)。テーマ スタイルは、すべてのテーマで一貫したトリガー セマンティクスを持つ必要はありません。 トリガーまたはアニメーションによってコントロール スタイルによって公開される動作は、テーマによって異なる場合があります。 コントロールユーザーは、コントロールが必ずしもすべてのテーマで特定の動作を実現するために同じメカニズムを使用するとは限らないことに注意する必要があります。 たとえば、あるテーマではホバー動作を表現するためにアニメーションを使用し、別のテーマではトリガーを使用します。 これにより、カスタマイズされたコントロールでの動作の保持に不整合が生じる可能性があります。 (たとえば、バックグラウンド プロパティを変更しても、その状態がトリガーを使用して表現されている場合、コントロールのホバー状態に影響しない可能性があります。ただし、アニメーションを使用してホバー状態が実装されている場合は、バックグラウンドに変更すると、アニメーションが回復不能に中断され、状態遷移が発生する可能性があります)。
テーマ スタイルは、すべてのテーマで一貫した "レイアウト" セマンティクスを持つ必要はありません。 たとえば、既定のスタイルでは、コントロールがすべてのテーマで同じサイズを占めることを保証したり、コントロールがすべてのテーマで同じコンテンツ余白/パディングを持っていることを保証したりする必要はありません。
こちらも参照ください
.NET Desktop feedback