XAML テンプレート コントロールをビルドする

この記事では、WinUI 3 用のテンプレート化された XAML コントロールを作成する手順について説明します。 テンプレート化されたコントロールは Microsoft.UI.Xaml.Controls.Control から継承され、XAML コントロール テンプレートを使用してカスタマイズできる視覚的な構造と視覚的な動作を持っています。

[前提条件]

  1. 開発環境をセットアップする ( Windows App SDK
  2. 手順に従って、最初の WinUI プロジェクトを作成してください。

C# と C++/WinRT アプリの両方から使用するために C# でスタンドアロン WinUI コンポーネントを作成するには、「Walkthrough: WinUI コントロールを使用して C# コンポーネントを作成し、C++ Windows App SDK アプリケーションから使用するに関する記事を参照してください。

  1. Windows アプリの開発を開始します
  2. 最新バージョンの C++/WinRT Visual Studio Extension (VSIX)

WinUI の空のアプリケーションを作成する (BgLabelControlApp)

まず、Microsoft Visual Studio で新しいprojectを作成します。 新しいプロジェクト ダイアログで、WinUI ブランク アプリ (パッケージ化) のプロジェクト テンプレートを選択し、適切な言語バージョンを選択します。 ファイル名が次の例のコードと一致するように、project名を "BgLabelControlApp" に設定します。

WinUI 空のアプリ (パッケージ化) プロジェクト テンプレート

WinUI 空のアプリ (パッケージ化) プロジェクト テンプレート

テンプレート 化されたコントロールをアプリに追加する

テンプレート化されたコントロールを追加するには、ツール バーの Project メニューをクリックするか、Solution Explorer でprojectを右クリックし、 [新しい項目の追加を選択します。

Visual C#->WinUI で、テンプレート コントロール テンプレートを選択します。 新しいコントロールに "BgLabelControl" という名前を指定し、[追加] をクリックします。

Visual C++->WinUI で、テンプレート コントロール テンプレートを選択します。 新しいコントロールに "BgLabelControl" という名前を指定し、[追加] をクリックします。 これにより、projectに 3 つの新しいファイルが追加されます。 BgLabelControl.h はコントロール宣言を含むヘッダーで、 BgLabelControl.cpp にはコントロールの C++/WinRT 実装が含まれています。 BgLabelControl.idl は、コントロールをランタイム クラスとしてインスタンス化できるようにするインターフェイス定義ファイルです。

BgLabelControl カスタム コントロール クラスを実装する

C# ファイルの BgLabelControl.cs では、コンストラクターによってコントロールの DefaultStyleKey プロパティが定義されていることに注意してください。 このキーは、コントロールのconsumerでテンプレートが明示的に指定されていない場合に使用される既定のテンプレートを識別します。 今作成しているコントロールの場合、キーの値は "type" です。 このキーは、後で汎用テンプレート ファイルを実装するときに使用します。

public BgLabelControl()
{
    this.DefaultStyleKey = typeof(BgLabelControl);
}

今作成しているテンプレート コントロールには、コード内か XAML 内で、またはデータ バインディングを使用してプログラムから設定できるテキスト ラベルがあります。 システムでコントロールのラベルのテキストを最新の状態に保つためには、それを DependencyPropety として実装する必要があります。 これを行うには、まず文字列プロパティを宣言し、それに Label という名前を付けます。 バッキング変数を使用する代わりに、GetValueSetValue を呼び出すことによって依存関係プロパティの値の設定と取得を行います。 これらのメソッドは、Microsoft.UI.Xaml.Controls.Control が継承する DependencyObject によって提供されます。

public string Label
{
    get => (string)GetValue(LabelProperty);
    set => SetValue(LabelProperty, value);
}

次に、依存関係プロパティを宣言し、それをシステムに登録するために、DependencyProperty.Register を呼び出します。 このメソッドでは、Label プロパティの名前と型、プロパティの所有者の型 (BgLabelControl クラス)、およびプロパティの既定値を指定します。

DependencyProperty LabelProperty = DependencyProperty.Register(
    nameof(Label), 
    typeof(string),
    typeof(BgLabelControl), 
    new PropertyMetadata(default(string), new PropertyChangedCallback(OnLabelChanged)));

依存関係プロパティを実装するために必要な手順はこの 2 つだけですが、この例では、OnLabelChanged イベントに対する省略可能なハンドラーを追加します。 このイベントは、プロパティ値が更新されるたびにシステムによって発生させられます。 この例では、新しいラベルのテキストが空の文字列であるかどうかを確認し、それに応じてクラス変数を更新します。

public bool HasLabelValue { get; set; }

private static void OnLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    BgLabelControl labelControl = d as BgLabelControl; //null checks omitted
    String s = e.NewValue as String; //null checks omitted
    if (s == String.Empty)
    {
        labelControl.HasLabelValue = false;
    }
    else
    {
        labelControl.HasLabelValue = true;
    }
}

依存関係プロパティのしくみについて詳しくは、「依存関係プロパティの概要」をご覧ください。

次の手順では、ランタイム クラスを実装するために、BgLabelControl.idlBgLabelControl.h、および BgLabelControl.cpp ファイルのコードを project ディレクトリに更新します。

テンプレート化されたコントロール クラスは XAML マークアップからインスタンス化され、そのためランタイム クラスになります。 完成したプロジェクトをビルドすると、MIDL コンパイラ (midl.exe) は BgLabelControl.idl ファイルを使用して、あなたのコンポーネントを利用する消費者が参照するコントロールのWindows Runtimeメタデータファイル (.winmd) を生成します。 ランタイム クラスの作成の詳細については、「 C++/WinRT を使用した API の作成」を参照してください。

作成するテンプレート 化されたコントロールは、コントロールのラベルとして使用される文字列である単一のプロパティを公開します。 BgLabelControl.idlの内容を次のコードに置き換えます。

// BgLabelControl.idl
namespace BgLabelControlApp
{
    runtimeclass BgLabelControl : Microsoft.UI.Xaml.Controls.Control
    {
        BgLabelControl();
        static Microsoft.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

上記の一覧は、依存関係プロパティ (DP) を宣言するときに従うパターンを示しています。 各 DP には 2 つの部分があります。 まず、DependencyProperty 型の読み取り専用静的プロパティを宣言します。 あなたのDPの名前にプロパティが加わります。 実装では、この静的プロパティを使用します。 次に、DP の型と名前を使用して、読み取り/書き込みインスタンス プロパティを宣言します。 添付プロパティ (DP ではなく) を作成する場合は、「 カスタム添付プロパティ」のコード例を参照してください。

BgLabelControl.h の内容を次のコードに置き換えます。

// BgLabelControl.h
#pragma once
#include "BgLabelControl.g.h"

namespace winrt::BgLabelControlApp::implementation
{
    struct BgLabelControl : BgLabelControlT<BgLabelControl>
    {
        BgLabelControl() { DefaultStyleKey(winrt::box_value(L"BgLabelControlApp.BgLabelControl")); }

        winrt::hstring Label()
        {
            return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
        }

        void Label(winrt::hstring const& value)
        {
            SetValue(m_labelProperty, winrt::box_value(value));
        }

        static Microsoft::UI::Xaml::DependencyProperty LabelProperty() { return m_labelProperty; }

        static void OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const&, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs const&);

    private:
        static Microsoft::UI::Xaml::DependencyProperty m_labelProperty;
    };
}
namespace winrt::BgLabelControlApp::factory_implementation
{
    struct BgLabelControl : BgLabelControlT<BgLabelControl, implementation::BgLabelControl>
    {
    };
}

上記のコードは 、Label プロパティと LabelProperty プロパティを実装し、 OnLabelChanged という名前の静的イベント ハンドラーを追加して依存関係プロパティの値への変更を処理し、 LabelProperty のバッキング フィールドを格納するプライベート メンバーを追加します。

次に、BgLabelControl.cppの内容を次のコードに置き換えます。

// BgLabelControl.cpp
#include "pch.h"
#include "BgLabelControl.h"
#if __has_include("BgLabelControl.g.cpp")
#include "BgLabelControl.g.cpp"
#endif

namespace winrt::BgLabelControlApp::implementation
{
    Microsoft::UI::Xaml::DependencyProperty BgLabelControl::m_labelProperty =
        Microsoft::UI::Xaml::DependencyProperty::Register(
            L"Label",
            winrt::xaml_typename<winrt::hstring>(),
            winrt::xaml_typename<BgLabelControlApp::BgLabelControl>(),
            Microsoft::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Microsoft::UI::Xaml::PropertyChangedCallback{ &BgLabelControl::OnLabelChanged } }
    );

    void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const& d, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs const& /* e */)
    {
        if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
        {
            // Call members of the projected type via theControl.

            BgLabelControlApp::implementation::BgLabelControl* ptr{ winrt::get_self<BgLabelControlApp::implementation::BgLabelControl>(theControl) };
            // Call members of the implementation type via ptr.
        }
    }
}

このチュートリアルでは OnLabelChanged コールバックを使用しませんが、プロパティ変更コールバックに依存関係プロパティを登録する方法を確認できるように提供されています。 OnLabelChanged の実装では、投影された基本型から派生投影型を取得する方法も示します (この場合、基本投影型は DependencyObject です)。 そして、投影された型を実装する型へのポインターを取得する方法を示しています。 その 2 番目の操作は、投影された型 (つまり、ランタイム クラスを実装するproject) を実装するprojectでのみ可能になります。

xaml_typename 関数は、WinUI project テンプレートに既定では含まれていない Windows.UI.Xaml.Interop 名前空間によって提供されます。 projectのプリコンパイル済みヘッダー ファイル (pch.h) に行を追加して、この名前空間に関連付けられているヘッダー ファイルを含めます。

// pch.h
...
#include <winrt/Windows.UI.Xaml.Interop.h>
...

BgLabelControl の既定のスタイルを定義する

テンプレート コントロールでは、コントロールのユーザーが明示的にスタイルを設定しない場合に使用される既定のスタイル テンプレートを提供する必要があります。 そのコンストラクターでは、コントロール自体に既定のスタイル キーが設定されます。

汎用テンプレート ファイルは "Generic.xaml" という名前で、projectの Themes フォルダーに配置する必要があります。 XAML フレームワークでテンプレート コントロールの既定のスタイルを検出するために、フォルダーとファイルの名前が必要です。

汎用テンプレート ファイルは、テンプレート コントロール をアプリに追加すると生成されます。

project ノードで、新しいフォルダー (フィルターではなくフォルダー) を作成し、"テーマ" という名前を付けます。 Themesで、Visual C++ > WinUI > リソース ディクショナリ (WinUI)の新しい項目を追加し、"Generic.xaml" と名付けます。

Generic.xaml の既定のコンテンツを削除し、以下のマークアップを貼り付けます。

<!-- \Themes\Generic.xaml -->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BgLabelControlApp">

    <Style TargetType="local:BgLabelControl" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:BgLabelControl">
                    <Grid Width="100" Height="100" Background="{TemplateBinding Background}">
                        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{TemplateBinding Label}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Style 要素の TargetType 属性は、BgLabelControlApp 名前空間内の BgLabelControl 型に設定されます。 この型は、コントロールのコンストラクターの DefaultStyleKey プロパティに指定された値と同じです。この値は、コントロールの既定のスタイルとして識別されます。

コントロール テンプレートの TextBlockText プロパティは、TemplateBinding マークアップ拡張を使用してコントロールの Label 依存関係プロパティにバインドされます。 Grid の背景は、Control クラスから継承された Background 依存関係プロパティにバインドされます。

メイン UI ページに BgLabelControl のインスタンスを追加する

MainWindow.xaml を開くと、メイン UI ページの XAML マークアップが含まれています。 Button 要素の直後 (StackPanel 内) に次のマークアップを追加します。

<local:BgLabelControl Background="Red" Label="Hello, World!"/>

また、MainWindow.h 型 (XAML マークアップのコンパイルと命令型コードの組み合わせ) が BgLabelControl テンプレートコントロール型を認識できるように、次のインクルード ディレクティブをに追加します。 別の XAML ページ BgLabelControl を使用する場合は、同じ include ディレクティブをそのページのヘッダー ファイルにも追加します。 または、プリコンパイル済みヘッダー ファイルに単一のインクルード ディレクティブを配置するだけです。

//MainWindow.h
...
#include "BgLabelControl.h"
...

次に、projectをビルドして実行します。 既定のコントロール テンプレートが、マークアップ内の BgLabelControl インスタンスの背景ブラシとラベルにバインドされていることがわかります。

テンプレート化されたコントロールの結果

コントロール メソッドのオーバーライド

テンプレート化されたコントロールは 、Control ランタイム クラスから派生します。このクラス自体は、基底ランタイム クラスからさらに派生します。 派生クラスでオーバーライドしてコントロールの動作をカスタマイズできる 、ControlFrameworkElementUIElement のオーバーライド可能なメソッドがあります。

C# では、オーバーライド可能なメソッドは protected override メソッドとして表示されます。 たとえば、レイアウト測定、テンプレート アプリケーション、ポインター入力処理、オートメーション ピアの作成をオーバーライドできます。

// Control overrides.
protected override void OnPointerPressed(PointerRoutedEventArgs e)
{
    base.OnPointerPressed(e);
    // Custom pointer handling...
}

// FrameworkElement overrides.
protected override Size MeasureOverride(Size availableSize)
{
    // Custom measure logic...
    return base.MeasureOverride(availableSize);
}

protected override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    // Retrieve template parts and set up event handlers...
}

// UIElement overrides.
protected override AutomationPeer OnCreateAutomationPeer()
{
    return new BgLabelControlAutomationPeer(this);
}

C++/WinRT では、オーバーライド可能な関数は仮想でも保護もされませんが、オーバーライドして独自の実装を提供することもできます。

// Control overrides.
void OnPointerPressed(Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& /* e */) const { ... };

// FrameworkElement overrides.
Windows::Foundation::Size MeasureOverride(Windows::Foundation::Size const& /* availableSize */) const { ... };
void OnApplyTemplate() const { ... };

// UIElement overrides.
Microsoft::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer() const { ... };

テンプレートを使用せずにコントロール ソース ファイルを生成する

このセクションでは、 テンプレート コントロール項目テンプレートを使用せずにカスタム コントロールを作成するために必要なソース ファイルを生成する方法について説明します。

最初に、新しい Midl ファイル (.idl) 項目をprojectに追加します。 Project メニューから、[新しい項目の追加]を選択します。検索ボックスに「MIDL」と入力して、.idl ファイル項目を検索します。 新しいファイルに BgLabelControl.idl 名前を付け、名前がこの記事の手順と一致するようにします。 BgLabelControl.idlの既定の内容を削除し、上記の手順で示したランタイム クラス宣言に貼り付けます。

新しい .idl ファイルを保存した後、次の手順では、テンプレート 化されたコントロールの実装に使用する.cppおよび .h 実装ファイルのWindows Runtime メタデータ ファイル (.winmd) とスタブを生成します。 ソリューションをビルドしてこれらのファイルを生成します。これにより、MIDL コンパイラ (midl.exe) によって、作成した .idl ファイルがコンパイルされます。 ソリューションが正常にビルドされず、Visual Studio出力ウィンドウにビルド エラーが表示されますが、必要なファイルが生成されることに注意してください。

スタブ ファイル BgLabelControl.h と BgLabelControl.cpp を \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ から project フォルダーにコピーします。 Solution Explorer で、[すべてのファイルを表示] がオンになっていることを確認します。 コピーしたスタブ ファイルを右クリックし、[include In Project をクリックします。

コンパイラは BgLabelControl.h の先頭にstatic_assert行を配置し、生成されたファイルがコンパイルされないようにBgLabelControl.cppします。 コントロールを実装するときは、project ディレクトリに配置したファイルからこれらの行を削除する必要があります。 このチュートリアルでは、ファイルの内容全体を上記のコードで上書きできます。

こちらも参照ください