WinUI - データバインディング(Bindings->Update)

C++ で WinUI 3 ライブラリを使う

この記事は試行錯誤の結果をまとめたもので、WinUI の正しい使い方ではないかもしれません。

下図のようにTextBox に入力した文字列をもう一方のTextBoxに反映する方法を考えます。

XAMLはこんな感じです。入力用のTextBoxと出力用の読み取り専用のTextBoxがあります。

<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
    <TextBox Header="String" />
    <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE70D;" Margin="8"/>
    <TextBox  IsReadOnly="True" />
</StackPanel>

データバインディングで実現するためには、文字列の変数が1つあればよいので、.idl ファイルを開き、文字列型のValueStringプロパティを宣言します。

[default_interface]
runtimeclass Binding1Page : Microsoft.UI.Xaml.Controls.Page
{
    Binding1Page();
    String ValueString;
}

それと同時にクラスに ValueString のセッターとゲッターを実装します。

struct Binding1Page : Binding1PageT<Binding1Page>
{
    Binding1Page();

    void ValueString(const hstring& value) { m_valueString = value; }
    hstring ValueString() { return m_valueString; }

    private:
        hstring m_valueString;
};

次に ValueString とバインディングするようにXAMLファイルを修正します。

<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
    <TextBox Header="String" Text="{x:Bind ValueString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE70D;" Margin="8"/>
    <TextBox Text="{x:Bind ValueString}" IsReadOnly="True" />
</StackPanel>

うまく動作しそうですが、ここまでのコードだと TextBox のテキストを変更する毎に ValueString のセッターは呼び出されていますが、出力用の TextBox は全然更新されません。

コードの値を画面に反映させるには、Bindings->Update(); を実行します。

ValueString のセッターの実装を値の変更があったときに Bindings->Update(); を実行するように変更すれば期待した動作になります。

void Binding1Page::ValueString(const hstring& value)
{
    if (m_valueString != value)
    {
        m_valueString = value;
        Bindings->Update();
    }
}

データバインディングを使わない方法

今回の例であればデータバインディングを使わない方が簡単かもしれません。

  1. コードから TextBox にアクセスできるように x:Name 属性で名前を付けます。
  2. テキストが変更されたときに呼び出される TextChanged イベントのイベントハンドラを入力用の TextBox に追加します。
  3. TextChanged イベントが発生したら、InputTextBox のテキストを OutputTextBox にコピーします。
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
    <TextBox x:Name="InputTextBox" TextChanged="InputTextBox_TextChanged" Header="String" />
    <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE70D;" Margin="8"/>
    <TextBox x:Name="OutputTextBox" IsReadOnly="True"/>
</StackPanel>
void winrt::NavigationView1::implementation::Binding1Page::InputTextBox_TextChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Controls::TextChangedEventArgs const& e)
{
    OutputTextBox().Text(InputTextBox().Text());
}

参考

WinUI 3 with C++ 入門 - ビリヤードが好きなプログラマー

Microsoft インターフェイス定義言語 3.0 の概要 - Windows UWP applications | Microsoft Learn

WinUI - データバインディングできるモデルを追加する

C++ で WinUI 3 ライブラリを使う

データバインディングできるモデル (class) をプロジェクトに追加しようとしたとき、その方法がわからず苦労したので紹介します。

Visual Studio のソリューションエクスプローラを開き、プロジェクトを選択した状態で右クリックのポップアップメニューを表示します。

ポップアップメニューの追加 → モジュールを選択します。

新しい項目の追加ダイアログで、ビューモデル (C++/WinRT) を追加します。

私は、Page やユーザーコントロールと違い「 WinUI 3 のプロジェクトなのに (C++/WinRT) の項目を追加してよいのだろうか」と悩んでました。

参考

WinUI 3 with C++ 入門 - ビリヤードが好きなプログラマー

WinUI - ナビゲーションのウィンドウに設定ボタンを表示する

C++ で WinUI 3 ライブラリを使う

NavigationView の IsSettingsVisible 属性を True にすると設定ボタンを表示します。

<NavigationView IsSettingsVisible="True">

メニューを選択したときのイベントハンドラでは、IsSettingsSelected, IsSettingsInvoked 関数で設定ボタンが押されたか判断できます。

void winrt::NavigationView1::implementation::MainWindow::navi_SelectionChanged(winrt::Microsoft::UI::Xaml::Controls::NavigationView const& sender, winrt::Microsoft::UI::Xaml::Controls::NavigationViewSelectionChangedEventArgs const& args)
{
    if (args.IsSettingsSelected())
    {
        // 設定メニューを選択した
    }
}


void winrt::NavigationView1::implementation::MainWindow::navi_ItemInvoked(winrt::Microsoft::UI::Xaml::Controls::NavigationView const& sender, winrt::Microsoft::UI::Xaml::Controls::NavigationViewItemInvokedEventArgs const& args)
{
    if (args.IsSettingsInvoked())
    {
        // 設定メニューを選択した
    }
}

参考

WinUI 3 with C++ 入門 - ビリヤードが好きなプログラマー

WinUI - 新しい UserControl を追加する

C++ で WinUI 3 ライブラリを使う

新しい UserControl をプロジェクトに追加しようとしたとき、その方法がわからず苦労したので紹介します。

Visual Studio のソリューションエクスプローラを開き、プロジェクトを選択した状態で右クリックのポップアップメニューを表示します。

ポップアップメニューの追加 → モジュールを選択します。

新しい項目の追加ダイアログで、ユーザーコントロール (WinUI 3) を追加します。

私は、空のユーザーコントロール (C++/WinRT) を追加して、「なんか違う」と悩んでました。

参考

WinUI 3 with C++ 入門 - ビリヤードが好きなプログラマー

WinUI - メニューを選択したときのイベントを実装する

C++ で WinUI 3 ライブラリを使う

NavigationVeiw のメニューを選択したときのイベントを処理する方法を紹介します。

NavigationView のメニューを選択すると ItemInvoked イベントが発生します。また選択しているメニューが変化したときは SelectionChanged イベントも発生します。

<NavigationView x:Name="navi" ItemInvoked="navi_ItemInvoked" SelectionChanged="navi_SelectionChanged" IsSettingsVisible="False">
    <NavigationView.MenuItems>
        <NavigationViewItem Content="Page A" Tag="PageA" />
        <NavigationViewItem Content="Page B" Tag="PageB" SelectsOnInvoked="False" >
            <NavigationViewItem.MenuItems>
                <NavigationViewItem Icon="Home" Content="Page B1" Tag="PageB1" />
                <NavigationViewItem Icon="World" Content="Page B2" Tag="PageB2" />
            </NavigationViewItem.MenuItems>
        </NavigationViewItem>
    </NavigationView.MenuItems>
        
    <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="4">
        <TextBox x:Name="ItemInvokedText" Header="ItemInvoked" />
        <TextBox x:Name="SelectionChangedText" Header="SelectionChanged" />
    </StackPanel>
</NavigationView>

どのメニューを選択しても同じイベントハンドラが呼び出されるので、Tag 等を設定してメニューを識別できるようにします。

void winrt::NavigationView1::implementation::MainWindow::navi_SelectionChanged(winrt::Microsoft::UI::Xaml::Controls::NavigationView const& sender, winrt::Microsoft::UI::Xaml::Controls::NavigationViewSelectionChangedEventArgs const& args)
{
    if (auto tag = args.SelectedItemContainer().Tag().try_as<hstring>(); tag)
    {
        SelectionChangedText().Text(*tag);
    }
}

void winrt::NavigationView1::implementation::MainWindow::navi_ItemInvoked(winrt::Microsoft::UI::Xaml::Controls::NavigationView const& sender, winrt::Microsoft::UI::Xaml::Controls::NavigationViewItemInvokedEventArgs const& args)
{
    if (auto tag = args.InvokedItemContainer().Tag().try_as<hstring>(); tag)
    {
        ItemInvokedText().Text(*tag);
    }
}

参考

WinUI 3 with C++ 入門 - ビリヤードが好きなプログラマー

NavigationView - Windows apps | Microsoft Learn

WinUI - ToggleSwitch のラベルをボタンの左側にする

C++ で WinUI 3 ライブラリを使う

ToggleSwitch のラベルをボタンの左側にするには FlowDirection に RightToLeft を設定します。

<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="4">
    <ToggleSwitch BorderBrush="Red" BorderThickness="1"/>
    <ToggleSwitch BorderBrush="Blue" BorderThickness="1" FlowDirection="RightToLeft" />
</StackPanel>

ToggleSwitch の領域がわかるように枠に色をつけています。

参考

WinUI 3 with C++ 入門 - ビリヤードが好きなプログラマー

ToggleButton クラス (Microsoft.UI.Xaml.Controls.Primitives) - Windows App SDK | Microsoft Learn

WinUI - 新しい Page を追加する

C++ で WinUI 3 ライブラリを使う

新しい Page をプロジェクトに追加しようとしたとき、その方法がわからず苦労したので紹介します。

Visual Studio のソリューションエクスプローラを開き、プロジェクトを選択した状態で右クリックのポップアップメニューを表示します。

ポップアップメニューの追加 → モジュールを選択します。

新しい項目の追加ダイアログで、空白のページ (WinUI 3) を追加します。

私は、空白のページ (C++/WinRT) を追加して「うまくいかない」と悩んでました。

参考

WinUI 3 with C++ 入門 - ビリヤードが好きなプログラマー