C++/WinRT を使用した XAML カスタム (テンプレート化) コントロール

Important

C++/WinRT でランタイム クラスを使用および作成する方法の理解をサポートする基本的な概念と用語については、「 C++/WinRTでの API の使用」および「C++/WinRT を使用した API の作成」を参照してください。

Windows アプリ SDKの最も強力な機能の 1 つは、XAML コントロールの種類に基づいてカスタム コントロールを作成するためにユーザー インターフェイス (UI) スタックが提供する柔軟性です。 XAML UI フレームワークには、 カスタム依存関係プロパティ添付プロパティコントロール テンプレートなどの機能が用意されており、機能豊富でカスタマイズ可能なコントロールを簡単に作成できます。 このトピックでは、C++/WinRT を使用してカスタム (テンプレート化) コントロールを作成する手順について説明します。

空のアプリを作成する (BgLabelControlApp)

まず、Microsoft Visual Studio で、新しいプロジェクトを作成します。 C++ プロジェクト用の 空のアプリパッケージ (デスクトップの WinUI 3) を作成し、その名前を BgLabelControlApp に設定し、(フォルダー構造がチュートリアルと一致するように) ソリューションとプロジェクトを同じディレクトリに配置 するチェック ボックスがオフになっていることを確認します。 Windows SDK の最新の一般公開バージョン (プレビュー版ではない) をターゲットにします。

このトピックの後のセクションでは、プロジェクトをビルドするように指示されます (ただし、それまではビルドしません)。

Note

C++/WinRT 開発用のVisual Studio (C++/WinRT Visual Studio拡張機能 (VSIX) のインストールと使用、NuGet パッケージ (プロジェクト テンプレートとビルド サポートを提供) など) の設定については、C++/WinRT のサポートVisual Studio参照してください。

カスタム (テンプレート化) コントロールを表す新しいクラスを作成します。 同じコンパイル ユニット内でクラスを作成して使用しています。 ただし、XAML マークアップからこのクラスをインスタンス化できるようにしたいと考えています。そのため、ランタイム クラスになります。 また、C++/WinRT を使用して作成と使用の両方を行います。

新しいランタイム クラスを作成する最初の手順は、新しい Midl File (.idl) 項目をプロジェクトに追加することです。 BgLabelControl.idl と名前を付けます。 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 の名前に Property を加えた名前になります。 実装では、この静的プロパティを使用します。 次に、DP の型と名前を使用して、読み取り/書き込みインスタンス プロパティを宣言します。 添付プロパティ (DP ではなく) を作成する場合は、「カスタム添付プロパティ」のコード例を参照してください。

Note

浮動小数点型の DP が必要な場合は、double にします(MIDL 3.0 では Double)。 型 float の DP を宣言して実装し(MIDL では Single)、その DP の値を XAML マークアップで設定すると、テキスト '<NUMBER>' から 'Windows.Foundation.Single' を作成できませんでした というエラーが発生します。

ファイルを保存します。 現時点ではプロジェクトは完了するまでビルドされませんが、 BgLabelControl ランタイム クラスを実装するソース コード ファイルが生成されるため、今すぐビルドすると便利です。 ここでビルドします (この段階で確認できるビルド エラーは、"未解決の外部シンボル" と関係があります)。

ビルド プロセス中に、midl.exe ツールが実行され、ランタイム クラスを記述するWindows ランタイムメタデータ ファイル (\BgLabelControlApp\Debug\BgLabelControlApp\Unmerged\BgLabelControl.winmd) が作成されます。 次に、 cppwinrt.exe ツールを実行して、ランタイム クラスの作成と使用をサポートするソース コード ファイルを生成します。 これらのファイルには、IDL で宣言した BgLabelControl ランタイム クラスの実装を開始するためのスタブが含まれています。 これらのスタブは \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.hBgLabelControl.cpp です。

スタブ ファイルの BgLabelControl.hBgLabelControl.cpp\BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ からプロジェクト フォルダー ( \BgLabelControlApp\BgLabelControlApp\) にコピーします。 ソリューション エクスプローラーで、[すべてのファイルを表示] がオンになっていることを確認します。 コピーしたスタブ ファイルを右クリックし、[include In Project をクリックします。

static_assertBgLabelControl.hの上部にBgLabelControl.cppが表示されます。削除する必要があります。 これでプロジェクトがビルドされます。

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

次に、\BgLabelControlApp\BgLabelControlApp\BgLabelControl.hBgLabelControl.cpp を開いて、ランタイムクラスを実装しましょう。 BgLabelControl.hで、コンストラクターを変更して既定のスタイル キーを設定し、LabelLabelProperty を実装し、OnLabelChanged という名前の静的イベント ハンドラーを追加して依存関係プロパティの値への変更を処理し、LabelProperty のバッキング フィールドを格納するプライベート メンバーを追加します。

追加すると、 BgLabelControl.h は次のようになります。 このコード 一覧をコピーして貼り付けると、 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>
    {
    };
}

BgLabelControl.cppで、次のように静的メンバーを定義します。 このコード 一覧をコピーして貼り付けると、 BgLabelControl.cppの内容を置き換えることができます。

// BgLabelControl.cpp
#include "pch.h"
#include "BgLabelControl.h"
#include "BgLabelControl.g.cpp"

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 番目の操作は、投影された型 (つまり、ランタイム クラスを実装するプロジェクト) を実装するプロジェクトでのみ可能になります。

Note

Windows SDK バージョン 10.0.17763.0 (Windows 10 バージョン 1809) 以降をインストールしていない場合は、winrt::get_self ではなく、上記の依存関係プロパティ変更イベント ハンドラーで winrt::from_abi を呼び出す必要があります。

BgLabelControl の既定のスタイルをデザインする

そのコンストラクターで、BgLabelControl 、それ自体の既定のスタイル キーを設定します。 しかし、既定のスタイル 何ですか? カスタム (テンプレート化) コントロールには、既定のスタイル (既定のコントロール テンプレートを含む) が必要です。このスタイルは、コントロールのコンシューマーがスタイルやテンプレートを設定していない場合に備えて、コントロール自体をレンダリングするために使用できます。 このセクションでは、既定のスタイルを含むマークアップ ファイルをプロジェクトに追加します。

Show All Files が引き続きオンになっていることを確認します (ソリューション エクスプローラー)。 プロジェクト ノードの下に新しいフォルダー (フィルターではなくフォルダー) を作成し、"テーマ" という名前を付けます。 Themesで、Visual C++>XAML>XAML ビューの新しい項目を追加し、"Generic.xaml" という名前を付けます。 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>

この場合、既定のスタイル が設定する唯一のプロパティはコントロール テンプレートです。 テンプレートは、四角形 (背景が XAML コントロール型のすべてのインスタンスにある Background プロパティにバインドされている) とテキスト要素 (テキストが BgLabelControl::Label 依存関係プロパティにバインドされている) で構成されます。

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

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

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

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

// MainPage.h
...
#include "BgLabelControl.h"
...

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

このチュートリアルでは、C++/WinRT のカスタム (テンプレート化) コントロールの簡単な例を示しました。 独自のカスタム コントロールを、豊富でフル機能の任意の方法で作成できます。 たとえば、カスタム コントロールは、編集可能なデータ グリッド、ビデオ プレーヤー、または 3D ジオメトリのビジュアライザーのように複雑な形式をとることができます。

MeasureOverrideOnApplyTemplate などのオーバーライド可能なメソッドの実装

C++/WinRT を使用した基本型の呼び出しとオーバーライドに関するセクションを参照してください。

重要な API