【Windows App SDK/C++】チェックボックスをTwoWayモードでバインドする

Windows App SDKC++を使用したアプリで、CheckBoxのIsCheckedプロパティをTwoWayモードでバインドします。ただそれだけです。 IsCheckedのプロパティをバインドすることで、ドメインロジックの層ではCheckBoxを意識することなく、ただバインドされたbool型の変数を参照すればよくなります。

前置き

twowayモードを使用しますが、データの流れとしてはGUIチェックボックスの状態をある変数の値に反映させる方向のみであり、例えばプログラムから画面のチェックボックスのチェック状態を操作するようなことは行いません。 この理由としては単にWindows App SDKのBinding Modeに画面から変数(ターゲットからソース)の向きのみを扱うモードが無いからです。

BindingMode 列挙型を見れば分かるように、OneTime/OneWay/TwoWayしかモードがありません。なのでCheckBoxのようにユーザが操作できるGUIの状態を変数にバインドするには必然的にTwoWayモードを使用することになります。

余談ですが.NETのAPIにはOneWayToSourceというターゲットからソースへの一方向のバインドを行うモードがあります。

アプリの動作

今回作成したアプリの挙動を説明します。

画面にはボタン、テキストブロック、チェックボックスの3つの要素があります。 ボタンをクリックすると、テキストの内容がチェックの有無に応じた値に更新されます。 チェックが入っていれば"Checked!"、チェックがなければ"Unchecked..."と表示されます。

クラス構成

一応ビューとビューモデルを分離しています。チェックボックスのIsCheckedプロパティとテキストブロックのTextプロパティはCheckBoxViewModelのメンバ変数にバインドされているので、ビュークラスの仕事はクリックイベントの処理のみです。

コード内容

プロジェクトはGithubにあげたのですべて見たい場合はそちらを確認していただくことにして、要点のみを説明します。

github.com

まずはxamlGUIの要素を作成します。 TextBlockはバインドした変数の内容を表示するだけなのでModeがOneWayになっているのに対し、CheckBoxのIsChecked要素はTwoWayとしています。

<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
        <TextBlock Text="{x:Bind ViewModel.Text, Mode=OneWay}"></TextBlock>
        <CheckBox Content="Do you agree?"
                  IsChecked="{x:Bind ViewModel.IsChecked, Mode=TwoWay}"/>
    </StackPanel>

次にCheckBoxViewModelの定義です。 idlファイルではXamlからアクセスできるようにプロパティのゲッター、セッターを宣言します。 idlファイルにいちいち書かないといけないのがC#で書くときに比べて冗長に感じるところです。。。 テキストを更新したタイミングでGUIに通知を送信するためにMicrosoft.UI.Xaml.Data.INotifyPropertyChangedを継承しています。

namespace TwoWayViewModel
{
    [Microsoft.UI.Xaml.Data.Bindable]
    runtimeclass CheckBoxViewModel : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
    {
        CheckBoxViewModel();
        Int32 MyProperty;
        Boolean IsChecked;
        String Text{ get; };
        void UpdateText();
    }
}

個人的に詰まったポイントがBooleanの表記です。 MIDL Predefined and Base Typesを確認し、boolean表記にしていたのですが、そうすると以下のエラーが発生します。

Classic WinRT IDL constructs cannot be used in Modern WinRT IDL types [context]: boolean

クラシックなWinRTとモダンなWinRTの違いがわからないので正確なことは言えないですが、Boolean表記とすることでビルドが通りました。

IsCheckedの実装は以下の通り、単にbool型の変数のゲッター、セッターです。

boolean IsChecked() { return is_checked_; };
void IsChecked(boolean val) { is_checked_ = val; };

boolean is_checked_ = true; // デフォルト値

これでis_checked変数にチェックボックスの値がバインドされます。 ソースからターゲットへのOneWayのバインドの場合は、値を更新したら通知イベントを発行する必要がありますが、ターゲットからソースへの場合はターゲット側の値が変化すれば自動で変数に反映されます。

あとはCheckBoxViewModelで処理を行う際にはチェックボックスを意識せずにis_checkedを参照すればよいです。簡単ですね。