2019 年 5 月

Volume 34 Number 5

[XAML]

カスタム XAML コントロール

Jerry Nixon Nixon

エンタープライズ開発者であるあなたは、SQL Server に精通しています。また、.NET Web サービスを理解しています。さらに、あなたにとって、美しい XAML インターフェイス (おそらく Windows Presentation Foundation (WPF) を使用した) の設計は朝飯前です。他の何千ものキャリア開発者と同様、あなたの履歴書は Microsoft の技術スキルで埋め尽くされ、あなたはこのような MSDN Magazine の記事を切り取り、かんばんボードにピン留めしていることでしょう。はさみを手に取ってください。この記事が手持ちのチップです。

XAML コントロールを使って専門知識を強化するときが来ました。XAML フレームワークは UI 開発用のコントロールの豊富なライブラリを提供していますが、必要なものを実現するためにはそれだけでは不十分です。この記事では、XAML カスタム コントロールを使用して必要な結果を出す方法を示します。

カスタム コントロール

XAML でカスタム コントロールを作成するには 2 つのアプローチがあります。ユーザー コントロールとテンプレート コントロールです。ユーザー コントロールは、再利用可能なレイアウトを作成するための、簡単でデザイナーに優しいアプローチです。テンプレート コントロールは、開発者向けのカスタマイズ可能な API を使用した柔軟なレイアウトを提供します。任意の言語と同様に、高度なレイアウトは、効率的に移動することが困難な何千行もの XAML を生成する可能性があります。カスタム コントロールは、レイアウト コードを削減するための効果的な戦略です。

正しいアプローチを選択すれば、アプリケーションでのコントロールの使用と再利用を成功させる助けになります。作業の開始前の考慮事項を以下に示します。

シンプルさ。簡単は必ずしもシンプルとは限りませんが、シンプルは常に簡単です。ユーザー コントロールはシンプルで簡単です。あらゆるレベルの開発者が、ほとんどドキュメントを参照せずに、ユーザー コントロールを提供できます。

設計経験。多くの開発者が XAML デザイナーを好んで使っています。テンプレート コントロールのレイアウトはデザイナーで構築できますが、テンプレート コントロールはデザイン時のエクスペリエンスを活用するユーザー コントロールです。

API サーフェス。直感的な API サーフェスを構築すれば、開発者はそれを簡単に利用することができます。ユーザー コントロールは、カスタム プロパティ、イベント、メソッドをサポートしますが、テンプレート コントロールは最も柔軟です。

柔軟なビジュアル。優れた既定のエクスペリエンスを提供すれば、開発者は簡単にコントロールを利用することができます。ただし、柔軟なコントロールは再テンプレート化されたビジュアルをサポートします。再テンプレート化をサポートするのは、テンプレート コントロールだけです。

まとめると、ユーザー コントロールはシンプルさと設計経験に最適ですが、テンプレート コントロールは最良の API サーフェスと最も柔軟なビジュアルを提供します。

ユーザー コントロールから始めて後からテンプレート コントロールに移行する場合は、いくつかの作業が必要ですが、それほど大変なことではありません。この記事では、ユーザー コントロールから始めて、テンプレート コントロールに移行します。多くの再利用可能なレイアウトに必要なものはユーザー コントロールだけだということを認識しておくことが重要です。基幹業務ソリューションを開いて、ユーザー コントロールとテンプレート コントロールの両方を探してみることもお勧めします。

新しいユーザー ストーリー

新しいアプリケーションでのエンドユーザー使用許諾契約 (EULA) に対する同意を新しいユーザーから得る必要があります。ご存じのように、EULA に同意したいと思っているユーザーはいません。それでも、法律上は、ユーザーが先に進む前に [同意します] を選択したことを確認する必要があります。したがって、EULA がわかりにくくても、XAML インターフェイスが明確で直感的であることを確認することになります。プロトタイピングを開始して、図 1 に示すように、TextBlock、CheckBox、Button を追加してから、検討を開始します。

プロトタイプ UI
図 1 プロトタイプ UI

XAML デザイナーでのプロトタイピングは高速で、簡単です。これは、ツーリングの習得に時間をかけた成果です。しかし、アプリケーション内のその他のフォームについてはどうでしょうか。この機能が他のどこかで必要になる場合があります。カプセル化は、コンシューマーに複雑なロジックを見せないようにするために使用される設計パターンです。DRY 原則がもう 1 つのコード再利用の焦点です。  XAML は、ユーザー コントロールとカスタム コントロールを通してどちらも提供します。XAML 開発者は、カスタム コントロールの方がユーザー コントロールより強力であるものの、シンプルではないことを知っています。ユーザー コントロールから始めることにします。たぶん、やり遂げられるでしょう。開発者からの警告: 無理です。

ユーザー コントロール

ユーザー コントロールは簡単です。また、一貫した再利用可能なインターフェイスとカスタムのカプセル化された分離コードを提供します。ユーザー コントロールを作成するには、図 2 に示すように、[新しい項目の追加] ダイアログから [ユーザー コントロール] を選択します。

[新しい項目の追加] ダイアログ
図 2 [新しい項目の追加] ダイアログ

ユーザー コントロールの多くは、別のコントロールの子です。ただし、そのライフサイクルがウィンドウやページのライフサイクルに似ているため、ユーザー コントロールを Window.Current.Content プロパティに設定された値にすることができます。ユーザー コントロールは、完全な機能を備えており、表示状態管理、内部リソース、およびその他の XAML フレームワークの不可欠な要素すべてをサポートします。ユーザー コントロールをビルドしても、使用可能な機能を侵害することはありません。目標は、表示状態管理、リソース、スタイル、データ バインディングのサポートを利用しながら、ページ上でユーザー コントロールを再利用することです。これらの XAML 実装は、シンプルで設計者に優しいのが特徴です。

<UserControl>
  <StackPanel Padding="20">
    <TextBlock>Lorem ipsum.</TextBlock>
    <CheckBox>I agree!</CheckBox>
    <Button>Submit</Button>
  </StackPanel>
</UserControl>

この XAML は、以前のプロトタイプをレンダリングし、ユーザー コントロールのシンプルさのみを示します。もちろん、まだカスタム動作はなく、宣言されたコントロールの動作のみが組み込まれています。

テキスト高速パス EULA は長いため、テキスト パフォーマンスを解決する必要があります。TextBlock (および TextBlock だけ) は、高速パス、少量のメモリ、CPU レンダリングを使用するように最適化されています。高速になるように構築されていますが、それを台無しにすることもできます。

<TextBlock Text="Optimized" />
<TextBlock>Not optimized</TextBlock>

<Run/> や <LineBreak /> などの TextBlock インライン コントロールを使用すると、最適化が破棄されます。CharacterSpacing、Line­StackingStrategy、TextTrimming などのプロパティでも同じことができます。複雑に思えるかもしれませんが、簡単なテストが用意されています。

Application.Current.DebugSettings
  .IsTextPerformanceVisualizationEnabled = true;

IsTextPerformanceVisualizationEnabled は、あまり知られていないデバッグ設定ですが、デバッグ時にアプリケーション内のどのテキストが最適化されるかを確認することができます。テキストが緑色でない場合は、調査が必要です。

Windows のすべてのリリースで、高速パスに影響するプロパティの数は減りましたが、まだ、パフォーマンスに予期せぬ悪影響を及ぼすプロパティが残っています。意図的にそうしなければ、これが問題になることはありません。

不変のルール ビジネス ロジックを常駐すべきという意見と同じくらい意見が分かれるところです。原則では、あまり変化しないルールをコントロールの近くに配置します。これは、ほとんどの場合、簡単かつ高速で、保守容易性を最適化します。

ところで、ビジネス ルールはデータ検証とは違います。データ入力に対する応答と、テキストの長さや数値の範囲のチェックは、単なる検証です。ルールは、ユーザー行動の類を制御します。

たとえば、銀行には、信用スコアが特定の値を下回る顧客には融資しないというビジネス ルールがあります。配管工には、特定の郵便番号以外の顧客には巡回しないというルールがあります。ルールは行動に関するものです。信用スコアが新規融資に与える影響のように、ルールが毎日変化する場合があります。一方で、整備士は 2014 年より古いスバルには決して触ろうとしないといった不変のルールもあります。

ここで、次の受け入れ基準について考えてみます。ユーザーは、CheckBox がオンになるまで、Button をクリックできません。これは、ルールであり、これ以上ないくらいほぼ不変です。これをコントロールの近くに実装しようと思います。

<StackPanel Padding="20">
  <TextBlock>Lorem ipsum.</TextBlock>
  <CheckBox x:Name="AgreeCheckBox">I agree!</CheckBox>
  <Button IsEnabled="{Binding Path=IsChecked,
    ElementName=AgreeCheckBox}">Submit1</Button>
  <Button IsEnabled="{x:Bind Path=AgreeCheckBox.IsChecked.Value,
    Mode=OneWay}">Submit2</Button>
</StackPanel>

このコードでは、データ バインディングが要件を完全に満たします。Submit1 Button では、従来の WPF (および UWP) データ バインディングが使用されます。Submit2 Button では、最新の UWP データ バインディングが使用されます。

図 3 で Submit2 が有効になっていることに注目してください。そのとおりになっていますか。さて、Visual Studio デザイナーでは、従来のデータ バインディングには、デザイン時にレンダリングできるというメリットがあります。今のところは、実行時にコンパイル済みのデータ バインディング (x:Bind) のみが出現します。従来のデータ バインディングとコンパイル済みのデータ バインディングのどちらを選ぶかは、最も難しい決断を強いられます。コンパイル済みのバインディングは高速なのに対して、従来のバインディングはシンプルです。コンパイル済みのバインディングは、XAML の難しいパフォーマンスの問題 (データ バインディング) を解決するために存在します。従来のバインディングは、実行時リフレクションが必要なため、本質的に遅く、スケールが困難です。

データ バインディングを使用したビジネス ルールの実装
図 3 データ バインディングを使用したビジネス ルールの実装

非同期バインディングなどの新しい機能が従来のバインディングに追加され、開発者を支援するいくつかのパターンが明らかになりました。ただし、UWP は WPF を引き継ぐ必要があったため、同じドラッグ操作の問題が残りました。留意点は次のとおりです。従来のバインディングを非同期モードで使用する機能が WPF から UWP に移植されませんでした。必要なものを読み込む必要がありますが、このことがエンタープライズ開発者にコンパイル済みのバインディングへの投資を促しています。コンパイル済みのバイディングでは、XAML コード ジェネレーターを利用して、自動的に分離コードが作成され、バインディング ステートメントと、実際のプロパティおよび実行時に想定されているデータ型が結合されます。

この結合のために、匿名オブジェクトまたは動的 JSON オブジェクトにバインドしようとして型が一致せずエラーになる可能性があります。このようなエッジ ケースは多くの開発者によって確認されているため、解消されています。

  • コンパイル済みのバインディングは、一定の制約はありますが、データ バインディングのパフォーマンスの問題を解決します。
  • 下位互換性によって、従来のバインディングのサポートが維持されますが、UWP 開発者にはより適切なオプションが提供されます。
  • データ バインディングの刷新や機能強化は、コンパイル済みのバインディングには施されますが、従来のバインディングには施されません。
  • 関数バインディングなどの機能は、マイクロソフトのバインディング戦略に明確に焦点が当てられたコンパイル済みのバインディングでしか使用できません。

それでも、従来のバインディングのシンプルさとデザイン時のサポートが、引数を存続させ、Microsoft 開発者のツーリング チームによるコンパイル済みのバイディングとその開発者エクスペリエンスの向上の継続を後押ししています。この記事では、どちらを選ぶかで天と地ほどの差があることに注意してください。従来のバインディングをデモするサンプルもあれば、コンパイル済みのバインディングを示すサンプルもあります。どれを選ぶかはあなた次第です。もちろん、大規模なアプリでは意思決定が最も重要です。

カスタム イベント XAML ではカスタム イベントを宣言できないため、分離コードで処理する必要があります。たとえば、送信ボタンのクリック イベントをユーザー コントロール上のカスタム クリック イベントに転送することができます。

public event RoutedEventHandler Click;
public MyUserControl1()
{
  InitializeComponent();
  SubmitButton.Click += (s, e)
    => Click?.Invoke(this, e);
}

ここでは、コードが、ボタンから RoutedEventArgs を転送する、カスタム イベントを発生させます。利用する開発者は、XAML 内の他のイベントと同様に、これらのイベントを宣言によって処理できます。

<controls:MyUserControl1 Click="MyUserControl1_Click" />

この利点は、利用する開発者が新しいパラダイムを学習する必要がないことです。カスタム コントロールとすぐに使えるファースト パーティ コントロールは機能的に同じ振る舞いをします。

カスタム プロパティ 利用する開発者が独自の EULA を指定できるようにするために、TextBlock 上で x:FieldModifier 属性を設定できます。これにより、既定のプライベート値から XAML コンパイル動作が変更されます。

<TextBlock x:Name="EulaTextBlock" x:FieldModifier="public" />

ただし、簡単だから適切というわけではありません。この方法は、ほとんど抽象化を提供しないため、開発者が内部構造を理解する必要があります。分離コードも必要です。そのため、このケースでは、属性アプローチを使用しません。

public string Text
{
  get => (string)GetValue(TextProperty);
  set => SetValue(TextProperty, value);
}
public static readonly DependencyProperty TextProperty =
  DependencyProperty.Register(nameof(Text), typeof(string),
    typeof(MyUserControl1), new PropertyMetadata(string.Empty));

同じように簡単で補足説明が要らないのが、TextBlock の Text プロパティにバインドされた依存関係プロパティ データです。これにより、利用する開発者は、カスタム Text プロパティの読み取り、書き込み、バインドを行うことができます。

<StackPanel Padding="20">
  <TextBlock Text="{x:Bind Text, Mode=OneWay}" />
  <CheckBox>I agree!</CheckBox>
  <Button>Submit</Button>
</StackPanel>

依存関係プロパティは、データ バインディングをサポートするために必要です。堅牢なコントロールは、データ バインディングなどの基本的なユース ケースをサポートします。加えて、依存関係プロパティは、コード ベースに 1 行を追加するだけです。

<TextBox Text="{x:Bind Text, Mode=TwoWay,
  UpdateSourceTrigger=PropertyChanged}" />

ユーザー コントロールでのカスタム プロパティに対する双方向データ バインディングは、INotifyPropertyChanged を使用せずにサポートされます。これは、バインディング フレームワークが監視している内部変更イベントを依存関係プロパティが発生させるためです。これは、独自の INotifyPropertyChanged といったところです。

上記コードでは、変更を記録するタイミングが UpdateSourceTrigger によって決定されることがわかります。使用可能な値は、Explicit、LostFocus、PropertyChanged です。後者は変更が加えられたときに発生します。

障害 利用する開発者は、ユーザー コントロールのコンテンツ プロパティを設定することができます。これは、直感的なアプローチですが、ユーザー コントロールではサポートされません。プロパティは既に宣言済みの XAML に設定されています。

<Controls:MyUserControl>
  Lorem Ipsum
</Controls:MyUserControl>

この構文によって、コンテンツ プロパティの TextBlock、CheckBox、Button が上書きされます。基本的な XAML ユース ケースの再テンプレート化を考慮すると、完全で堅牢なエクスペリエンスをユーザー コントロールで実現できません。ユーザー コントロールは簡単ですが、制御や拡張性をほとんど提供しません。直感的な構文と再テンプレート化のサポートは日常的なエクスペリエンスの一部です。次は、テンプレート コントロールを検討する番です。

テンプレート コントロール

XAML は、メモリ消費、パフォーマンス、ユーザー補助、視覚的な一貫性が大幅に向上されています。開発者は、柔軟性があるという理由で XAML を好んで使っています。テンプレート コントロールがその典型例です。

テンプレート コントロールは全く新しいものを定義することができますが、その多くは既存のコントロールを組み合わせたものです。この TextBlock、CheckBox、Button を一緒に示す例は、従来のシナリオです。

ところで、テンプレート コントロールとカスタム コントロールを混同しないでください。テンプレート コントロールはカスタム レイアウトです。カスタム コントロールは、カスタム スタイル指定を使用せず既存のコントロールを継承する単なるクラスです。既存のコントロールにメソッドやプロパティを追加するだけでいい場合は、カスタム コントロールが有力な選択肢になります。そのビジュアルとロジックは既に機能しており、単にそれらを拡張するだけです。

コントロール テンプレート コントロールのレイアウトは ControlTemplate によって定義されます。この特殊なリソースは、実行時に適用されます。すべてのボックスとボタンが ControlTemplate 内に存在します。コントロールのテンプレートは、Template プロパティによって簡単にアクセスできます。この Template プロパティは読み取り専用ではありません。開発者は、このプロパティを、コントロールのビジュアルと動作を特定のニーズに合わせて変換するカスタム ControlTemplate に設定できます。これが再テンプレート化の能力です。

<ControlTemplate>
  <StackPanel Padding="20">
    <ContentControl Content="{TemplateBinding Content}" />
    <CheckBox>I agree!</CheckBox>
    <Button>Submit1</Button>
  </StackPanel>
</ControlTemplate>

ControlTemplate XAML は、他のレイアウト宣言に似ています。上記コードでは、特殊な TemplateBinding マークアップ拡張に注目してください。この特殊なバインディングが一方向テンプレート操作用に調整されています。Windows 10 バージョン 1809 以降は、x:Bind 構文が UWP ControlTemplate 定義でサポートされます。これにより、テンプレートで、高性能でコンパイル済みの双方向関数バインディングが可能になります。TemplateBinding はほとんどのケースで正常に機能します。

Generic.xaml テンプレート コントロールを作成するには、[新しい項目の追加] ダイアログで [テンプレート コントロール] を選択します。これにより、3 つのファイル (XAML ファイル、その分離コード、および ControlTemplate を保持する themes/generic.xaml) が導入されます。themes/generic.xaml ファイルは WPF と同じです。これは特殊なファイルです。フレームワークがこのファイルを自動的にアプリのリソースにマージします。ここで定義するリソースは、アプリケーション レベルでスコープ設定されます。

<Style TargetType="controls:MyControl">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="controls:MyControl" />
    </Setter.Value>
  </Setter>
</Style>

ControlTemplate は、暗黙的なスタイル (キーを伴わないスタイル) を使用して適用されます。明示的なスタイルにはコントロールに適用するためのキーがあります。暗黙的なスタイルは TargetType に基づいて適用されます。そのため、DefaultStyleKey を設定する必要があります。

public sealed class MyControl : Control
{
  public MyControl() => DefaultStyleKey = typeof(MyControl);
}

このコードは、コントロールに暗黙的に適用されるスタイルを決定する、DefaultStyleKey を設定します。ここでは、ControlTemplate 内の TargetType の対応する値に設定します。

<ControlTemplate TargetType="controls:MyControl">
  <StackPanel Padding="20">
    <TextBlock Text="{TemplateBinding Text}" />
    <CheckBox Name="AgreeCheckbox">I agree!</CheckBox>
    <Button IsEnabled="{Binding IsChecked,
      ElementName=AgreeCheckbox}">Submit</Button>
  </StackPanel>
</ControlTemplate>

TemplateBinding は、TextBlock の Text プロパティをユーザー コントロールからテンプレート コントロールにコピーされたカスタム依存関係プロパティにバインドします。TemplateBinding は、一方向の非常に効率的で概ね最適なオプションです。

図 4 にデザイナーでの作業の結果を示します。ControlTemplate で宣言されたカスタム レイアウトがカスタム コントロールに適用され、バインディングが実行され、デザイン時にレンダリングされます。

 

<controls:MyControl Text="My outside value." />

内部プロパティを使用して透過的にプレビューする
図 4 内部プロパティを使用して透過的にプレビューする

カスタム コントロールを使用するための構文はシンプルです。この構文を開発者がインライン テキストを使用できるように改良します。これは、要素のコンテンツを設定するための最も直感的な構文です。XAML には、図 5 に示すように、この構文に役立つクラス属性があります。

図 5 コンテンツ プロパティを設定するためのクラス属性の使用

[ContentProperty(Name = "Text")]
public sealed class MyControl : Control
{
  public MyControl() => DefaultStyleKey = typeof(MyControl);
  public string Text
  {
    get => (string)GetValue(TextProperty);
    set => SetValue(TextProperty, value);
  }
  public static readonly DependencyProperty TextProperty =
    DependencyProperty.Register(nameof(Text), typeof(string),
      typeof(MyControl), new PropertyMetadata(default(string)));
}

Windows.UI.Xaml.Markup 名前空間に属している ContentProperty 属性に注目してください。この属性は、XAML で宣言されたインライン コンテンツを書き込む必要があるプロパティを直接示します。そのため、ここでは、次のようにコンテンツを宣言することができます。

<controls:MyControl>
  My inline value!
</controls:MyControl>

実に美しい。テンプレート コントロールは、最も直感的だと思われる方法でコントロールの相互作用と構文を設計できる柔軟性を提供します。図 6 に、コントロールに ContentProperty を導入した結果を示します。

設計プレビュー
図 6 設計プレビュー

ContentControl 以前はカスタム プロパティを作成してコントロールのコンテンツにマップする必要がありましたが、XAML はそのために既に構築されたコントロールを備えています。このコントロールは ContentControl と呼ばれており、そのプロパティ名は Content です。ContentControl は、視覚化と遷移を処理するための ContentTemplate プロパティと ContentTransition プロパティも提供します。Button、CheckBox、Frame、および多くの標準的な XAML コントロールが ContentControl を継承します。自分のコードにも含めることができます。Text の代わりに Content を使用するだけです。

public sealed class MyControl2 : ContentControl
{
  // Empty
}

このコードでは、Content プロパティを使用してカスタム コントロールを作成するための簡潔な構文に注目してください。ContentControl は、宣言時に、ContentPresenter として自動的にレンダリングされます。これは、高速で簡単なソリューションです。ただし、注意点があります。ContentControl は、XAML 内のリテラル文字列をサポートしません。これはコントロールでリテラル文字列をサポートするという目標に反するため、ここでは Control に焦点を合わせて、別の機会に ContentControl を検討します。

内部コントロールへのアクセス x:Name ディレクティブは、分離コードで自動生成されたフィールド名を宣言します。チェックボックスに MyCheckBox という x:Name を付けると、ジェネレーターが MyCheckBox という名前のクラスでフィールドを作成します。

対照的に、x:Key (リソース専用) は、フィールドを作成しません。リソースが最初に使用されるまで未解決の型のディクショナリに追加します。リソースでは、x:Key によってパフォーマンスが向上します。

x:Name はバッキング フィールドを作成するため、ControlTemplate では使用できません。テンプレートがすべてのバッキング クラスから切り離されます。代わりに、Name プロパティを使用します。

Name は、Control の先祖である FrameworkElement 上の依存関係プロパティです。x:Name が使用できない場合に使用します。Name と x:Name は、FindName のために、一定の範囲内で相互排他的です。

FindName は、名前でオブジェクトを検索するための XAML メソッドです。Name または x:Name で動作します。このメソッドは、分離コード内では信頼できますが、GetTemplateChild を使用する必要があるテンプレート コントロール内では信頼できません。

protected override void OnApplyTemplate()
{
  if (GetTemplateChild("AgreeCheckbox") is CheckBox c)
  {
    c.Content = "I really agree!";
  }
}

GetTemplateChild は、ControlTemplate によって作成されたコントロールを検索するために OnApplyTemplate オーバーライドで使用されるヘルパー メソッドです。内部コントロールへの参照を検索するために使用します。

テンプレートの変更 コントロールの再テンプレート化はシンプルですが、特定の名前を持つコントロールを想定するためのクラスを構築しました。新しいテンプレートでこの依存関係が維持されることを保証する必要があります。新しい ControlTemplate を構築しましょう。

<ControlTemplate
  TargetType="controls:MyControl"
  x:Key="MyNewStyle">

ControlTemplate に少しの変更を加えるだけで済みます。ゼロから始める必要はありません。Visual Studio の [ドキュメント アウトライン] ウィンドウで、任意のコントロールを右クリックして、その現在のテンプレートのコピーを抽出します (図 7 を参照)。

コントロール テンプレートの抽出
図 7 コントロール テンプレートの抽出

依存関係を維持する場合は、新しい ControlTemplate がコントロールのビジュアルと動作をすっかり変えてしまう可能性があります。コントロール上での明示的なスタイルの宣言は、既定の暗黙的なスタイルを無視するようにフレームワークに指示します。

<controls:MyControl Style="{StaticResource MyControlNewStyle}">

ただし、この ControlTemplate の書き換えでは警告が表示されます。コントロールの設計者と開発者は、ユーザー補助機能とローカライズ機能を慎重にサポートする必要があります。これらは誤って簡単に削除されます。

TemplatePartAttribute カスタム コントロールが想定した名前付き要素と通信可能な場合に重宝します。エッジ ケースでは、一部の名前付き要素が必須の場合があります。WPF には、TemplatePartAttribute があります。

[TemplatePart (
  Name = "EulaTextBlock",
  Type = typeof(TextBlock))]
public sealed class MyControl : Control { }

この構文は、コントロールが外部の開発者に内部の依存関係をどのように伝達するかを示しています。このケースでは、ControlTemplate 内の EulaTextBlock という名前で TextBlock を想定します。カスタム コントロールで、想定したビジュアル状態を指定することもできます。

[TemplateVisualState(
  GroupName = "Visual",
  Name = "Mouseover")]
public sealed class MyControl : Control { }

TemplatePart は、TemplateVisualState の Blend がカスタム テンプレートを作成しながら、想定に反して開発者を誘導するために使用されます。ControlTemplate は、このような属性に照らして検証することができます。10240 以降の WinRT には、これらの属性が含まれています。UWP はこれらの属性を使用できますが、Blend for Visual Studio は使用できません。これらは、適切で前向きなプラクティスですが、依然としてドキュメントが最良のアプローチです。

ユーザー補助 ファースト パーティ XAML コントロールは、美しく、互換性があって、アクセス可能なように、慎重に設計され、テストされています。ユーザー補助要件は、今や、最優先事項であり、すべてのコントロールのリリース要件でもあります。

ファースト パーティ コントロールを再テンプレート化すると、開発チームによって慎重に追加されたユーザー補助機能を危険に晒すことになります。これらは、正しく理解することが難しく、誤解されがちです。コントロールの再テンプレート化を選択する場合は、フレームワークのユーザー補助機能とそれを実装するテクニックに精通している必要があります。そうでない場合は、その価値の大部分を失うことになります。

リリース要件としてユーザー補助機能を追加すれば、永続する障害を抱えている人だけでなく、一時的に障害を抱える人も支援することになります。また、ファースト パーティ コントロールを再テンプレート化するときのリスクも軽減されます。

やりましたね。

ユーザー コントロールからテンプレート コントロールにアップグレードした後は、ほとんど新しいコードを導入しませんでした。しかし、多くの機能が追加されました。全体として何が実現されたかを見てみましょう。

カプセル化。コントロールは、利用する開発者がアプリケーション全体で簡単に再利用できるカスタムのビジュアルと動作がバンドルされたさまざまなコントロールのコレクションです。

ビジネス ロジック。コントロールには、ユーザー ストーリーの受け入れ基準を満たすビジネス ルールが組み込まれています。不変のルールをコントロールの近くに配置して、豊富なデザイン時エクスペリエンスもサポートしました。

カスタム イベント。コントロールは、開発者がレイアウトの内部構造を理解していなくても、コントロールと相互運用ができるようにする「クリック」などのコントロール固有のカスタム イベントを公開します。

カスタム プロパティ。コントロールは、利用する開発者がレイアウトの内容に影響を与えられるようにするためのプロパティを公開します。これは XAML データ バインディングを完全にサポートする方法で実現されました。

API 構文。コントロールは、開発者がリテラル文字列を使用した直接的なやり方でその内容を宣言できるようにする直感的なアプローチをサポートします。これを実現するために ContentProperty 属性を利用しました。

テンプレート。コントロールには、直感的なインターフェイスをレイアウトする既定の ControlTemplate が付属しています。ただし、コンシューマー開発者が必要に応じてビジュアルをカスタマイズできるようにするために XAML 再テンプレート化がサポートされます。

これで終わりではありません。

コントロールでは、決して多くはありませんが、レイアウトに少しだけ注意を払ったり (大量のテキストをスクロールしなければならない場合など)、いくつかのプロパティ (チェックボックスの内容など) が必要だったりします。私は驚くほど綿密です。

コントロールは、XAML のネイティブ機能である表示状態管理をサポートできます。この機能を使用すれば、サイズ変更イベントやフレームワーク イベント (マウスオーバーなど) に基づいてプロパティを変更することができます。成熟したコントロールには表示状態があります。

コントロールはローカライズをサポートできます。UWP のネイティブ機能は、アクティブなロケールでフィルター処理された RESW 文字列とコントロールを関連付ける x:Uid ディレクティブを使用します。成熟したコントロールはローカライズをサポートします。

コントロールは、新しいテンプレートを使用しなくてもビジュアルを更新するのに役立つ外部スタイル定義をサポートできます。これは、共有ビジュアルを取り込むことができ、テーマと BasedOn スタイルを利用できます。成熟したコントロールはスタイルを共有または再利用します。

まとめ

XAML での UI のプロトタイピングは高速です。ユーザー コントロールは、シンプルで再利用可能なレイアウトを簡単に作成します。テンプレート コントロールの場合は、より洗練された機能を備えたシンプルで再利用可能なレイアウトを作成するために少しだけ余分な作業が必要です。適切なアプローチは、開発者次第であり、少しの知識と多くの経験に基づきます。実験。ツーリングを学べば学ぶほど、生産性が向上します。

2002 年に Windows フォームが Visual Basic 6 を引き継いだのと同様に、2006 年に WPF が Windows フォームを引き継ぎました。WPF には、UI 用の宣言型言語である XAML が付属しています。Microsoft の開発者は、XAML のようなものを見たことがありませんでした。今では、Xamarin と UWP が XAML を iOS、Android、HoloLens、Surface Hub、Xbox、IoT、および最新のデスクトップにもたらしています。事実、XAML は、現在、Windows OS 自体を構築するテクノロジになっています。

世界中の開発者が、非常に生産的で柔軟な XAML を好んで使っています。Microsoft のエンジニアも同じです。独自のアプリだけでなく、Windows 10 も XAML を使用して開発しています。未来は明るく、ツーリングは強力で、テクノロジは以前より親しみやすくなっています。


Jerry Nixon 氏は、Microsoft の商用ソフトウェア エンジニア リングのシニア ソフトウェア エンジニア兼リード アーキテクトです。彼は、20 年間、ソフトウェアの開発と設計に従事してきました。講演者であり、主催者であり、教師であり、作成者である Nixon 氏は DevRadio のホストでもあります。彼の 1 日のほとんどは、3 人の娘に「スタートレック」の背景やエピソード プロットを教えることに費やされています。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Daniel Jacobson 氏、Dmitry Lyalin 氏、Daren May 氏、Ricardo Minguez Pablos 氏に心より感謝いたします。


この記事について MSDN マガジン フォーラムで議論する