C++/WinRT による XAML カスタム (テンプレート化) コントロール
重要
C++/WinRT でランタイム クラスを使用および作成する方法についての理解をサポートするために重要な概念と用語については、「C++/WinRT での API の使用」と「C++/WinRT での API の作成」を参照してください。
ユニバーサル Windows プラットフォーム (UWP) の最も強力な機能の 1 つは、XAML Control 型に基づいてカスタム コントロールを作成できるユーザー インターフェイス (UI) スタックの柔軟性です。 XAML UI フレームワークには、カスタム依存関係プロパティ、添付プロパティ、コントロール テンプレートなどの機能が用意されており、豊富な機能でカスタマイズ可能なコントロールを簡単に作成できます。 このトピックでは、C++ /WinRT を使用してカスタム (テンプレート) コントロールを作成する手順について説明します。
空のアプリを作成する (BgLabelControlApp)
まず、Microsoft Visual Studio で、新しいプロジェクトを作成します。 空のアプリ (C++/WinRT) を作成し、名前を BgLabelControlApp にして、(フォルダー構造がチュートリアルと一致するように) [ソリューションとプロジェクトを同じディレクトリに配置する] がオフになっていることを確認します。 Windows SDK の最新の一般公開された (プレビュー以外の) バージョンを対象とします。
このトピックの後半で、プロジェクトをビルドするよう指示します (ただし、それまでビルドしないでください)。
注意
C++/WinRT Visual Studio Extension (VSIX) と NuGet パッケージ (両者が連携してプロジェクト テンプレートとビルドをサポート) のインストールと使用など、C++/WinRT 開発用に Visual Studio を設定する方法については、Visual Studio での C++/WinRT のサポートに関する記事を参照してください。
これから、カスタム (テンプレート) コントロールを表す新しいクラスを作成します。 同じコンパイル ユニット内のクラスを作成および使用しています。 ただし、このクラスを XAML マークアップからインスタンス化できるようにするため、ランタイム クラスにします。 また、その作成と使用のどちらにも C++/WinRT を使用します。
新しいランタイム クラスの作成の最初の手順は、新しい Midl ファイル (.idl) 項目をプロジェクトに追加することです。 BgLabelControl.idl
と名前を付けます。 BgLabelControl.idl
の既定の内容を削除し、このランタイム クラスの宣言に貼り付けます。
// BgLabelControl.idl
namespace BgLabelControlApp
{
runtimeclass BgLabelControl : Windows.UI.Xaml.Controls.Control
{
BgLabelControl();
static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
String Label;
}
}
上記の一覧は、依存関係プロパティ (DP) を宣言するときに従うパターンを示しています。 各 DP には 2 つの部分があります。 まず、型 DependencyProperty の読み取り専用の静的プロパティを宣言します。 これは、実際の DP の名前に Property を加えたものです。 実装ではこの静的プロパティを使用します。 次に、DP の型と名前持つ読み取りおよび書き込みインスタンス プロパティを宣言します。 (DP ではなく) 添付プロパティを作成する場合は、「カスタム添付プロパティ」のコード例を参照してください。
注意
浮動小数点型の DP が必要な場合は、double
(MIDL 3.0 では Double
) にします。 型 float
(MIDL では Single
) の DP を宣言して実装し、XAML マークアップでその DP の値を設定すると、エラー "テキスト '<NUMBER>' から 'Windows.Foundation.Single' を作成できませんでした" が発生します。
ファイルを保存します。 現時点ではプロジェクトは完成するまでビルドされませんが、ここでビルドすることは有用です。それによって、BgLabelControl ランタイム クラスを実装するためのソース コード ファイルが生成されるからです。 それでは、今すぐビルドしてみましょう (この段階で表示されることが予想されるビルド エラーは、"未解決の外部シンボル" に関係します)。
ビルド プロセス中に、midl.exe
ツールが実行されて、ランタイム クラスを記述する Windows ランタイム メタデータ ファイル (\BgLabelControlApp\Debug\BgLabelControlApp\Unmerged\BgLabelControl.winmd
) が作成されます。 次に、cppwinrt.exe
ツールが実行され、ランタイム クラスの作成と使用をサポートするソース コード ファイルが生成されます。 これらのファイルには、IDL で宣言した BgLabelControl ランタイム クラスの実装を開始するためのスタブが含まれています。 これらのスタブは \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.h
と BgLabelControl.cpp
です。
スタブ ファイル BgLabelControl.h
と BgLabelControl.cpp
を \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\
からプロジェクト フォルダー \BgLabelControlApp\BgLabelControlApp\
にコピーします。 ソリューション エクスプローラーで、[すべてのファイルを表示] がオンであることを確認します。 コピーしたスタブ ファイルを右クリックし、[プロジェクトに含める] をクリックします。
BgLabelControl.h
と BgLabelControl.cpp
の先頭に static_assert
がありますが、これを削除する必要があります。 これで、プロジェクトがビルドされます。
BgLabelControl カスタム コントロール クラスを実装する
ここで、\BgLabelControlApp\BgLabelControlApp\BgLabelControl.h
と BgLabelControl.cpp
を開いてランタイム クラスを実装してみましょう。 BgLabelControl.h
で、コンストラクターを変更して既定のスタイル キーを設定し、Label と LabelProperty を実装し、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 Windows::UI::Xaml::DependencyProperty LabelProperty() { return m_labelProperty; }
static void OnLabelChanged(Windows::UI::Xaml::DependencyObject const&, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const&);
private:
static Windows::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
{
Windows::UI::Xaml::DependencyProperty BgLabelControl::m_labelProperty =
Windows::UI::Xaml::DependencyProperty::Register(
L"Label",
winrt::xaml_typename<winrt::hstring>(),
winrt::xaml_typename<BgLabelControlApp::BgLabelControl>(),
Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &BgLabelControl::OnLabelChanged } }
);
void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::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 番目の操作は、当然ながら、プロジェクションが実行された型を実装するプロジェクト (つまり、ランタイム クラスを実装するプロジェクト) でのみ可能です。
注意
Windows SDK バージョン 10.0.17763.0 (Windows 10 バージョン 1809) 以降をインストールしていない場合は、winrt::get_self ではなく、上記のイベント ハンドラーを変更して依存関係プロパティで winrt::from_abi を呼び出す必要があります。
BgLabelControl の既定のスタイルを設計する
そのコンストラクター内で、BgLabelControl によってそれ自体の既定のスタイル キーが設定されます。 しかし、既定のスタイルとは何でしょうか。 カスタム (テンプレート) コントロールには、コントロールのコンシューマーがスタイルやテンプレートを設定しない場合にレンダリングするために使用できる既定のコントロール テンプレートが含まれている、既定のスタイルを指定する必要があります。 このセクションでは、既定のスタイルを含むマークアップ ファイルをプロジェクトに追加します。
(ソリューション エクスプローラーで) [すべてのファイルを表示] がまだオンになっていることを確認します。 プロジェクト ノードで、新しいフォルダー (フィルターではなくフォルダー) を作成し、"Themes" という名前を付けます。 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>
この場合、既定のスタイルに設定される唯一のプロパティはコントロール テンプレートです。 テンプレートは、1 つの正方形 (背景は XAML Control 型のすべてのインスタンスに設定される Background プロパティにバインドされています) と、1 つのテキスト要素 (テキストは BgLabelControl::Label 依存関係プロパティにバインドされています) で構成されます。
メイン UI ページに BgLabelControl のインスタンスを追加します
MainPage.xaml
を開くと、メイン UI ページの XAML マークアップが含まれています。 Button 要素の直後 (StackPanel 内) に次のマークアップを追加します。
<local:BgLabelControl Background="Red" Label="Hello, World!"/>
また、MainPage 型 (XAML マークアップと命令型コードのコンパイルの組み合わせ) が BgLabelControl カスタム コントロール型を認識するように、MainPage.h
に次の include ディレクティブを追加します。 他の XAML ページから BgLabelControl を使用する場合は、そのページのヘッダー ファイルにも同じ include ディレクティブを追加します。 または、単にプリコンパイル済みヘッダー ファイルに 1 つの include ディレクティブを追加します。
// MainPage.h
...
#include "BgLabelControl.h"
...
ここでプロジェクトをビルドして実行します。 既定のコントロール テンプレートが、マークアップの BgLabelControl インスタンスの背景ブラシとラベルにバインドされることがわかります。
このチュートリアルでは、C++/WinRT のカスタム (テンプレート) コントロールの簡単な例を示しました。 必要に応じて、ご自身のカスタム コントロールをリッチで全機能を備えたものにすることができます。 たとえば、カスタム コントロールは、編集可能なデータ グリッド、ビデオ プレーヤー、または 3D ジオメトリのビジュアライザーのように複雑な形式にすることができます。
MeasureOverride や OnApplyTemplate などの "オーバーライド可能な" 関数の実装
「C++/WinRT を使用した基本型の呼び出しとオーバーライド」のセクションを参照してください。