XAML コントロール: C++/WinRT プロパティへのバインド

XAML コントロールに効果的にバインドできるプロパティは、監視可能なプロパティと呼ばれます。 この概念は、オブザーバー パターンと呼ばれるソフトウェアの設計パターンに基づいています。 このトピックでは、C++/WinRT で監視可能なプロパティを実装する方法と、これらに XAML コントロールをバインドする方法を示します (背景情報については、「データ バインディング」をご覧ください)。

重要

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

プロパティに対する監視可能とはどういう意味ですか?

たとえば、BookSku という名前のランタイム クラスに Title という名前のプロパティがあるとします。 Title の値が変わるたびに BookSkuINotifyPropertyChanged::PropertyChanged イベントを発生させる場合、Title は監視可能なプロパティです。 そのプロパティ (存在する場合) のどれが監視可能かを決定するのは BookSku の動作 (イベントを発生させるまたは発生させない) です。

XAML テキスト要素またはコントロールは、これらのイベントにバインドして処理できます。 このような要素またはコントロールでは、更新された値を取得して、新しい値を表示するためにそれ自体を更新することで、イベントが処理されます。

注意

C++/WinRT Visual Studio Extension (VSIX) と NuGet パッケージ (両者が連携してプロジェクト テンプレートとビルドをサポート) のインストールと使用については、Visual Studio での C++/WinRT のサポートに関する記事を参照してください。

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

まず、Microsoft Visual Studio で、新しいプロジェクトを作成します。 空のアプリ (C++/WinRT) プロジェクトを作成し、Bookstore と名前を付けます。 [ソリューションとプロジェクトを同じディレクトリに配置する] がオフになっていることを確認します。 Windows SDK の最新の一般公開された (プレビュー以外の) バージョンを対象とします。

監視可能なタイトルのプロパティを持つブックを表す、新しいクラスを作成します。 同じコンパイル ユニット内のクラスを作成および使用しています。 ただし、XAML からこのクラスへバインドできるようにしたいため、このクラスはランタイム クラスにします。 また、その作成と使用のどちらにも C++/WinRT を使用します。

新しいランタイム クラスの作成の最初の手順は、新しい Midl ファイル (.idl) 項目をプロジェクトに追加することです。 新しい項目に BookSku.idl.という名前を指定します。 BookSku.idl の既定の内容を削除し、このランタイム クラスの宣言に貼り付けます。

// BookSku.idl
namespace Bookstore
{
    runtimeclass BookSku : Windows.UI.Xaml.Data.INotifyPropertyChanged
    {
        BookSku(String title);
        String Title;
    }
}

注意

自分のビュー モデル クラス (実際に、自分のアプリケーション内で宣言したランタイム クラス) は、基底クラスから派生させる必要はありません。 上記で宣言されている BookSku クラスはその一例です。 このクラスではインターフェイスが実装されますが、これは基底クラスから派生したものではありません。

アプリケーション内で宣言したランタイム クラスのうち、基底クラスから派生したものは、"構成可能" クラスと呼ばれます。 また、構成可能クラスに関しては制約があります。 Visual Studio および Microsoft Store が送信を検証するために使用する Windows App Certification Kit テストにアプリケーションが合格するには (そして結果的に、アプリケーションが Microsoft Store に正常に取り込まれるには)、構成可能クラスを最終的に Windows 基底クラスから派生させる必要があります。 つまり、継承階層の最上位にあるクラスは、Windows.* 名前空間から取得された型である必要があります。 基底クラスからランタイム クラスを派生させる必要がある場合 (たとえば、派生元のご自分のすべてのビュー モデルに対して BindableBase クラスを実装するために)、Windows.UI.Xaml.DependencyObject から派生させることができます。

ビュー モデルはビューを抽象化したものであるため、ビュー (XAML マークアップ) に直接バインドされています。 データ モデルはデータを抽象化したものであり、自分のビュー モデルからのみ使用され、XAML に直接バインドされることはありません。 そのため、自分のデータ モデルをランタイム クラスとしてではなく、C++ の構造体またはクラスとして宣言することができます。 それらを MIDL 内で宣言する必要はなく、任意の継承階層を自由に使用することができます。

ファイルを保存し、プロジェクトをビルドします。 ビルドが完了するわけではありませんが、いくつかの必要な処理が行われます。 具体的には、ビルド プロセス中に、midl.exe ツールが実行されて、ランタイム クラスを記述する Windows ランタイム メタデータ ファイルが作成されます (ファイルはディスク上 (\Bookstore\Debug\Bookstore\Unmerged\BookSku.winmd) に配置されます)。 次に、cppwinrt.exe ツールが実行され、ランタイム クラスの作成と使用をサポートするソース コード ファイルが生成されます。 これらのファイルには、IDL で宣言した BookSku ランタイム クラスの実装を開始するためのスタブが含まれています。 後でディスク上で見つけますが、これらのスタブは \Bookstore\Bookstore\Generated Files\sources\BookSku.hBookSku.cpp です。

ここでは、Visual Studio でプロジェクト ノードを右クリックし、[エクスプローラーでフォルダーを開く] をクリックします。 これにより、エクスプローラーでプロジェクト フォルダーが開きます。 これで、\Bookstore\Bookstore\ フォルダーの内容が表示されます。 そこから \Generated Files\sources\ フォルダーに 移動し、スタブ ファイル BookSku.hBookSku.cpp をクリップボードにコピーします。 プロジェクト フォルダー (\Bookstore\Bookstore\) まで戻り、先ほどコピーした 2 つのファイルを貼り付けます。 最後に、ソリューション エクスプローラーで、プロジェクト ノードを選択した状態で、[すべてのファイルを表示] がオンであることを確認します。 コピーしたスタブ ファイルを右クリックし、[プロジェクトに含める] をクリックします。

BookSku を実装する

ここで、\Bookstore\Bookstore\BookSku.hBookSku.cpp を開いてランタイム クラスを実装してみましょう。 まず、BookSku.hBookSku.cpp の先頭に static_assert がありますが、これを削除する必要があります。

次に、BookSku.h で、次の変更を行います。

  • 既定のコンストラクターで、= default= deleteに変更します。 これは、既定のコンストラクターを必要としないためです。
  • このタイトル文字列を格納するプライベート メンバーを追加します。 winrt::hstring 値を受け取るコンストラクターがあることに注意してください。 この値はタイトル文字列です。
  • タイトルが変更されたときに発生させるイベント用に、別のプライベート メンバーを追加します。

これらの変更を行うと、BookSku.h は次のようになります。

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

namespace winrt::Bookstore::implementation
{
    struct BookSku : BookSkuT<BookSku>
    {
        BookSku() = delete;
        BookSku(winrt::hstring const& title);

        winrt::hstring Title();
        void Title(winrt::hstring const& value);
        winrt::event_token PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& value);
        void PropertyChanged(winrt::event_token const& token);
    
    private:
        winrt::hstring m_title;
        winrt::event<Windows::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
    };
}
namespace winrt::Bookstore::factory_implementation
{
    struct BookSku : BookSkuT<BookSku, implementation::BookSku>
    {
    };
}

BookSku.cpp では、次のように関数を実装します。

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

namespace winrt::Bookstore::implementation
{
    BookSku::BookSku(winrt::hstring const& title) : m_title{ title }
    {
    }

    winrt::hstring BookSku::Title()
    {
        return m_title;
    }

    void BookSku::Title(winrt::hstring const& value)
    {
        if (m_title != value)
        {
            m_title = value;
            m_propertyChanged(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Title" });
        }
    }

    winrt::event_token BookSku::PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
    {
        return m_propertyChanged.add(handler);
    }

    void BookSku::PropertyChanged(winrt::event_token const& token)
    {
        m_propertyChanged.remove(token);
    }
}

Title ミューテーター関数では、現在の値と異なる値が設定されているかどうかを確認します。 そのようになっている場合は、タイトルを更新して、変更されたプロパティの名前に等しい引数で INotifyPropertyChanged::PropertyChanged イベントも発生させます。 これにより、ユーザー インターフェイス (UI) は、どのプロパティの値を再クエリするのか認識できるようになります。

これを確認する場合は、プロジェクトがもう一度ビルドされます。

BookstoreViewModel を宣言および実装する

メイン XAML ページが、メイン ビュー モデルにバインドされます。 またそのビュー モデルは、BookSku 型のいずれかを含む、いくつかのプロパティを持つようになります。 この手順では、メイン ビュー モデル ランタイム クラスを宣言および実装します。

BookstoreViewModel.idl という名前の新しい Midl ファイル (.idl) 項目を追加します。 ただし、「ランタイム クラスを Midl ファイル (.idl) にファクタリングする」も参照してください。

// BookstoreViewModel.idl
import "BookSku.idl";

namespace Bookstore
{
    runtimeclass BookstoreViewModel
    {
        BookstoreViewModel();
        BookSku BookSku{ get; };
    }
}

保存してビルドします (ビルドが完了するわけではありませんが、ビルドを実行する理由はスタブ ファイルをもう一度生成することです)。

BookstoreViewModel.hBookstoreViewModel.cpp を、Generated Files\sources フォルダーからプロジェクト フォルダーにコピーし、プロジェクトに追加します。 これらのファイルを開き (static_assert をもう一度削除する)、下に示すようにランタイム クラスを実装します。 BookstoreViewModel.h で、BookSku の実装型 (winrt::Bookstore::implementation::BookSku) を宣言する BookSku.h をインクルードする方法に注意してください。 そして、既定のコンストラクターから、= default を削除します。

注意

BookstoreViewModel.hBookstoreViewModel.cpp についての次の一覧では、m_bookSku データ メンバーを構築する既定の方法がコードで示されています。 これは、日付を最初のリリースの C++/WinRT に戻すためのメソッドです。少なくとも、このパターンについて理解しておくことをお勧めします。 C++/WinRT バージョン 2.0 以降では、"均一の構築" と呼ばれる最適化された形式の構築が用意されています (「C++/WinRT 2.0 の新機能と変更点」を参照してください)。 このトピックの後半では、均一の構築の例を示します。

// BookstoreViewModel.h
#pragma once
#include "BookstoreViewModel.g.h"
#include "BookSku.h"

namespace winrt::Bookstore::implementation
{
    struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
    {
        BookstoreViewModel();

        Bookstore::BookSku BookSku();

    private:
        Bookstore::BookSku m_bookSku{ nullptr };
    };
}
namespace winrt::Bookstore::factory_implementation
{
    struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel, implementation::BookstoreViewModel>
    {
    };
}
// BookstoreViewModel.cpp
#include "pch.h"
#include "BookstoreViewModel.h"
#include "BookstoreViewModel.g.cpp"

namespace winrt::Bookstore::implementation
{
    BookstoreViewModel::BookstoreViewModel()
    {
        m_bookSku = winrt::make<Bookstore::implementation::BookSku>(L"Atticus");
    }

    Bookstore::BookSku BookstoreViewModel::BookSku()
    {
        return m_bookSku;
    }
}

注意

m_bookSku の型は投影型 (winrt::Bookstore::BookSku) であり、winrt::make で使用するテンプレート パラメーターは実装型 (winrt::Bookstore::implementation::BookSku) です。 この場合も、make は投影型のインスタンスを返します。

これで、プロジェクトがもう一度ビルドされます。

BookstoreViewModel 型のプロパティを MainPage に追加する

MainPage.idl を開きます。ここではメインの UI ページを表すランタイム クラスを宣言しています。

  • import ディレクティブを追加して、BookstoreViewModel.idl をインポートします。
  • BookstoreViewModel 型の MainViewModel という名前の読み取り専用プロパティを追加します。
  • MyProperty プロパティを削除します。
// MainPage.idl
import "BookstoreViewModel.idl";

namespace Bookstore
{
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

ファイルを保存します。 プロジェクトでビルドが完了するわけではありませんが、ここでビルドすることは有用です。それにより、MainPage ランタイム クラスを実装するソース コード ファイル (\Bookstore\Bookstore\Generated Files\sources\MainPage.h および MainPage.cpp) が再生成されるからです。 それでは、今すぐビルドしてみましょう。 この段階で表示されることが予想されるビルド エラーは、'MainViewModel': は 'winrt::Bookstore::implementation::MainPage' のメンバーではありませんというものです。

BookstoreViewModel.idl のインクルードを省略した場合 (上記の MainPage.idl のリストを参照)、エラー expecting < near "MainViewModel" が表示されます。 もう 1 つのヒントは、同じ名前空間 (コード リストに表示されている名前空間) 内にすべての型が残されているか確認することです。

表示されると予想されるエラーを解決するには、生成されたファイル (\Bookstore\Bookstore\Generated Files\sources\MainPage.h および MainPage.cpp) から MainViewModel プロパティ用のアクセサー スタブを \Bookstore\Bookstore\MainPage.h および MainPage.cpp にコピーする必要があります。 その手順は次のとおりです。

\Bookstore\Bookstore\MainPage.h で、以下の手順を実行します。

  • BookstoreViewModel の実装型 (winrt::Bookstore::implementation::BookstoreViewModel) を宣言する BookstoreViewModel.h をインクルードします。
  • ビュー モデルを格納するプライベート メンバーを追加します。 プロパティ アクセサー関数 (およびメンバー m_mainViewModel) は、BookstoreViewModel (Bookstore::BookstoreViewModel) の投影型について実装されていることに注意してください。
  • 実装型はアプリケーションと同じプロジェクト (コンパイル ユニット) にあるため、std::nullptr_t を使うコンストラクターのオーバーロードによって m_mainViewModel を作成します。
  • MyProperty プロパティを削除します。

注意

MainPage.hMainPage.cpp についての次の一覧では、m_mainViewModelデータ メンバーを構築する既定の方法がコードで示されています。 次のセクションでは、代わりに均一の構築を使用するバージョンを示します。

// MainPage.h
...
#include "BookstoreViewModel.h"
...
namespace winrt::Bookstore::implementation
{
    struct MainPage : MainPageT<MainPage>
    {
        MainPage();

        Bookstore::BookstoreViewModel MainViewModel();

        void ClickHandler(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&);

    private:
        Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
    };
}
...

\Bookstore\Bookstore\MainPage.cpp で、下記のリストに示すように、次の変更を行います。

  • winrt::make を (BookstoreViewModel 実装型で) 呼び出して、投影型 BookstoreViewModel の新しいインスタンスを m_mainViewModel に割り当てます。 前に説明したように、BookstoreViewModel コンストラクターでは、新しい BookSku オブジェクトがプライベート データ メンバーとして作成され、そのタイトルは最初に L"Atticus" に設定されます。
  • ボタンのイベント ハンドラー (ClickHandler) では、本のタイトルを公開されたタイトルに更新します。
  • MainViewModel プロパティのアクセサーを実装します。
  • MyProperty プロパティを削除します。
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"

using namespace winrt;
using namespace Windows::UI::Xaml;

namespace winrt::Bookstore::implementation
{
    MainPage::MainPage()
    {
        m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
        InitializeComponent();
    }

    void MainPage::ClickHandler(Windows::Foundation::IInspectable const& /* sender */, Windows::UI::Xaml::RoutedEventArgs const& /* args */)
    {
        MainViewModel().BookSku().Title(L"To Kill a Mockingbird");
    }

    Bookstore::BookstoreViewModel MainPage::MainViewModel()
    {
        return m_mainViewModel;
    }
}

均一の構築

winrt::make ではなく均一の構築を使用するには、次のように MainPage.hm_mainViewModel を 1 回だけ宣言および初期化します。

// MainPage.h
...
#include "BookstoreViewModel.h"
...
struct MainPage : MainPageT<MainPage>
{
    ...
private:
    Bookstore::BookstoreViewModel m_mainViewModel;
};
...

次に、MainPage.cppMainpage.xaml コンストラクターでは、コード m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>(); は必要ありません。

均一の構築の詳細とコード例については、「均一コンストラクションのオプトインと、実装への直接アクセス」を参照してください。

Title プロパティにボタンをバインドする

MainPage.xaml を開きます。ここには、メイン UI ページの XAML マークアップが含まれています。 下記のリストに示すように、ボタンから名前を削除し、その Content プロパティ値を文字式からバインド式に変更します。 バインド式の Mode=OneWay プロパティ (ビュー モデルから UI へ一方向) に注意してください。 このプロパティが無いと、UI はプロパティの変更イベントに応答しません。

<Button Click="ClickHandler" Content="{x:Bind MainViewModel.BookSku.Title, Mode=OneWay}"/>

ここでプロジェクトをビルドして実行します。 ボタンをクリックして Click イベント ハンドラーを実行します。 そのハンドラーがブックの Title ミューテーター関数を呼び出します。そのミューテーターがイベントを発生させるので、UI は Title プロパティが変更されたことを認識します。ボタンはそのプロパティの値を再クエリして、そのボタン独自の Content 値を更新します。

C++/WinRT で {Binding} マークアップ拡張を使用する

現在リリースされている C++/WinRT のバージョンの場合、{Binding} マークアップ拡張を使用できるようにするには、ICustomPropertyProvider および ICustomProperty インターフェイスを実装する必要があります。

要素間のバインド

ある XAML 要素のプロパティを、別の XAML 要素のプロパティにバインドできます。 マークアップでどのようになるかを示す例を以下に示します。

<TextBox x:Name="myTextBox" />
<TextBlock Text="{x:Bind myTextBox.Text, Mode=OneWay}" />

Midl ファイル (.idl) で読み取り専用プロパティとして名前付き XAML エンティティ myTextBox を宣言する必要があります。

// MainPage.idl
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
    MainPage();
    Windows.UI.Xaml.Controls.TextBox myTextBox{ get; };
}

これが必要な理由を次に示します。 XAML コンパイラで検証する必要があるすべての型は ({X:bind} で使われるものを含みます)、Windows メタデータ (WinMD) から読み取られます。 開発者が行う必要があるのは、読み取り専用のプロパティを Midl ファイルに追加することだけです。 自動的に生成される XAML コードビハインドで実装が提供されるので、実装は行わないでください。

XAML マークアップからのオブジェクトの使用

XAML {x:Bind} マークアップ拡張の使用によって使用されるすべてのエンティティは、IDL で公開されている必要があります。 さらに、XAML マークアップに、マークアップにも含まれる別の要素への参照が含まれている場合は、そのマークアップの getter が IDL に存在する必要があります。

<Page x:Name="MyPage">
    <StackPanel>
        <CheckBox x:Name="UseCustomColorCheckBox" Content="Use custom color"
             Click="UseCustomColorCheckBox_Click" />
        <Button x:Name="ChangeColorButton" Content="Change color"
            Click="{x:Bind ChangeColorButton_OnClick}"
            IsEnabled="{x:Bind UseCustomColorCheckBox.IsChecked.Value, Mode=OneWay}"/>
    </StackPanel>
</Page>

ChangeColorButton 要素は、バインディングによって UseCustomColorCheckBox 要素を参照します。 したがって、このページの IDL は、バインディングにアクセスできるようにするためには UseCustomColorCheckBox という名前の読み取り専用プロパティを宣言する必要があります。

UseCustomColorCheckBox の Click イベント ハンドラー デリゲートは従来の XAML デリゲート構文を使用するため、IDL のエントリは必要ありません。実装クラスでパブリックにする必要があるだけです。 一方、ChangeColorButton には {x:Bind} Click イベント ハンドラーもあり、これは IDL に移動する必要もあります。

runtimeclass MyPage : Windows.UI.Xaml.Controls.Page
{
    MyPage();

    // These members are consumed by binding.
    void ChangeColorButton_OnClick();
    Windows.UI.Xaml.Controls.CheckBox UseCustomColorCheckBox{ get; };
}

UseCustomColorCheckBox プロパティの実装を指定する必要はありません。 これは XAML コード ジェネレーターによって行われます。

ブール値へのバインド

これは診断モードで行うことができます。

<TextBlock Text="{Binding CanPair}"/>

true または false が C++/CX で表示されますが、C++/WinRT では Windows.Foundation.IReference`1<Boolean> が表示されます。

ブール値にバインドするときは、代わりに x:Bind を使用します。

<TextBlock Text="{x:Bind CanPair}"/>

Windows 実装ライブラリ (WIL) を使用する

Windows 実装ライブラリ (WIL) には、バインド可能なプロパティの記述を容易にするヘルパーが用意されています。 WIL ドキュメントの「プロパティへの通知」を参照してください。

重要な API