WinUI3もMVVMで実装する(MVVM Toolkit)
はじめに
以前の記事で.NET MAUIをMVVM実装するためにCommunityToolkit.Mvvm
を使用した。
CommunityToolkit.Mvvm
はWinUI3でも使用可能なので、同様にMVVMでの実装をしてみる。
環境
Windows 11
Visual Studio 2022
.NET 8.0
Microsoft.WindowsAppSDK 1.7.250606001
CommunityToolkit.Mvvm 8.4.0
プロジェクト作成
「空のアプリ、パッケージ化(デスクトップのWinUi3)」を選択。
ビルド、実行できることを確認。ボタンクリックするとテキストが変わる。
パッケージ化しない場合
MSIXではなく、.exe
形式にする場合。
プロジェクトファイルに以下の設定を追加する。
<Project ...>
...
<PropertyGroup>
...
<WindowsPackageType>None</WindowsPackageType>
...
</PropertyGroup>
...
</Project>
上記を設定せずに「Unpackaged」を選んでビルドするとエラーになる。
ComunityToolkit.Mvvmをインストール
MVVMを自前で実装すると冗長になるところを自動生成してくれるライブラリ。
NuGetでインストールする。
方針
現在は以下の通り、コードビハインドに処理が記述されている。
これをMVVMパターンにするため、ViewModelのプロパティとコマンドをバインドする実装に変更していく。
MainWindow.xaml
<Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
MainWindow.xaml.cs
namespace WinUiApp
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
private void myButton_Click(object sender, RoutedEventArgs e)
{
myButton.Content = "Clicked";
}
}
}
MainWindowViewModel.csを作成
ソリューションエクスプローラーでクラスを作成
プロジェクト名を右クリック > 追加 > クラス
-
partial classにする
-
ObservableObjectを継承
-
ObservableProperty
→ソースジェネレータによってコードが生成され双方向バインディングが可能になる。 -
RelayCommand
付与するとコマンドが自動生成される。
コマンド名はプレフィックスon
が存在する場合は削除され、末尾にCommand
が追加される。
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace WinUiApp
{
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private string buttonText = "Click Me";
[RelayCommand]
private void OnButtonClicked()
{
ButtonText = "Clicked!";
}
}
}
MainPage.xaml.csを修正
- コードビハインドの不要なコードを削除する。
- ViewModelをプロパティとして設定。
using Microsoft.UI.Xaml;
namespace WinUiApp
{
public sealed partial class MainWindow : Window
{
public MainWindowViewModel ViewModel { get; } = new();
public MainWindow()
{
this.InitializeComponent();
}
}
}
MainWindow.xamlを修正
- ボタンの
Text
とコマンド
のバインドを設定。 - クリックイベントを削除
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="WinUiApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WinUiApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="WinUiApp">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Content="{x:Bind ViewModel.ButtonText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Command="{x:Bind ViewModel.ButtonClickedCommand}" />
</StackPanel>
</Window>
バインド方法
今回使用した静的バインドx:Bind
ではなく、動的バインドBinding
も使用可能。
🔧 {x:Bind}ではなく{Binding}を使う場合
using Microsoft.UI.Xaml;
namespace WinUiApp
{
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
}
}
WindowにXAML上でバインドすることはできないため、以下の例ではPage
のDataContext
を使用している。
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="WinUiApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WinUiApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="WinUiApp">
<Page>
<Page.DataContext>
<local:MainWindowViewModel />
</Page.DataContext>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Content="{Binding ButtonText}" Command="{Binding ButtonClickedCommand}" />
</StackPanel>
</Page>
</Window>
動的バインドは動的にDataContextを変える等の柔軟性に優れ、静的バインドはパフォーマンスや安全性に優れるとされる。
比較
特徴/項目 | {Binding}(動的) | {x:Bind}(静的) |
---|---|---|
解決タイミング | ランタイム | コンパイルタイム |
バインドソース | DataContext ベース | コードビハインドのプロパティが主 |
型安全性 | 低い(文字列指定) | 高い(コンパイルチェックあり) |
パフォーマンス | 遅い | 速い |
動的切り替え対応 | あり | 基本なし |
デフォルトモード | OneWay | OneTime |
双方向バインド対応 | あり | あり(明示指定が必要) |
動作確認
ビルド、実行する。
初期コードと同じようにクリックでメッセージが変わることを確認。
コマンドにパラメータを渡す
ViewModelのクリック時の処理に引数を追加。
[RelayCommand]
private void OnButtonClicked(string name)
{
ButtonText = $"Clicked by {name}";
}
- MainWindow.xamlにテキストボックスを追加。
- 入力された文字列をクリックコマンドのパラメータとして渡す。
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="WinUiApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WinUiApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="WinUiApp">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox x:Name="NameInput" Width="200" PlaceholderText="名前を入力" />
<Button Content="{x:Bind ViewModel.ButtonText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Command="{x:Bind ViewModel.ButtonClickedCommand}"
CommandParameter="{x:Bind NameInput.Text, Mode=OneWay}" />
</StackPanel>
</Window>
実行し動作確認。
まとめ
WinUIとMAUIでは実装方法が少し異なる部分はあるものの、CommunityToolkit.Mvvm
を使用することで効率良くMVVMパターンで実装することができた。
Discussion