WPF MVVMサンプル(足し算)
1)概要
WPFでMVVMを使った簡単なサンプルを作ります。
数字を2つ入力してボタンを押すと合計が表示されます。
MVVMライブラリにはWCT MVVM Toolkit(Microsoft.Toolkit.Mvvm)を使用します。

2)環境
- Windows 10 Version 21H1
- .NET Framework 4.7.2 / .NET 5.0
- Visual Studio 2019 Version 16.10.2
- WPF
- Microsoft.Toolkit.Mvvm Version 7.0.2
- ModernWpfUI Version 0.9.4
3)プロジェクトの作成
MVVM Toolkitを使ったプロジェクトはWindows Template Studioでも作れますが、
Template Studioは使わずに一から作っていきます。
.NET Framework 4.7.2の場合
新規プロジェクトでWPFアプリ(.NET Framework)を選択します。
検索ボックスにwpfと入力すると探しやすいです。

プロジェクト名にMVVMSample001と入力し、フレームワークに.NET Framework 4.7.2を選択します。

.NET 5.0の場合
新規プロジェクトでWPFアプリケーションを選択します。

プロジェクト名にMVVMSample001と入力します。

ターゲットフレームワークに.NET 5.0を選択します。

4)プロジェクトの設定
.NET Framework 4.7.2の場合
[ツール]-[オプション]-[NuGetパッケージマネージャー]-[既定のパッケージ管理方式]でPackageReferenceを指定します。

.NET 5.0の場合
プロジェクトをダブルクリックしてcsprojファイルを開きます。
TargetFrameworkの値をnet5.0-windows10.0.18362.0に編集します。

5)NuGetパッケージ追加
以下のパッケージをNuGetで追加します。
Microsoft.Toolkit.MvvmModernWpfUI
6)ファイル準備
MainWindow.xamlを削除します。

プロジェクト直下にViewModelsフォルダとViewsフォルダを作成します。
ViewModelsフォルダにクラスMainWindowViewModel.csを追加します。
Viewsフォルダにウィンドウ(WPF)MainWindow.xamlを追加します。

7)画面デザイン変更
App.xamlに
xmlns:ui="http://schemas.modernwpf.com/2019"を追加し、
StartupUriをViews/MainWindow.xamlに修正し、
Application.ResourcesにResourceDictionaryを追加します。
<Application
x:Class="MVVMSample001.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MVVMSample001"
xmlns:ui="http://schemas.modernwpf.com/2019"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ThemeResources />
<ui:XamlControlsResources />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
</ResourceDictionary>
</Application.Resources>
</Application>
MainWindow.xamlに
xmlns:ui="http://schemas.modernwpf.com/2019"と
ui:WindowHelper.UseModernWindowStyle="True"を追加します。
<Window
x:Class="MVVMSample001.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MVVMSample001.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019"
Title="MainWindow"
Width="800"
Height="450"
ui:WindowHelper.UseModernWindowStyle="True"
mc:Ignorable="d">
<Grid>
</Grid>
</Window>
8)ViewModelの作成
MainWindowViewModel.csを以下のように編集します。
-
ObservableObjectを継承するようにします。 - 計算に使う2つの入力エリア、
Value1,Value2プロパティを定義します。 - 計算結果に使う
Resultプロパティを定義します。 - 計算実行用のコマンド
CalculateCommandを定義します。 - コンストラクタで計算コマンドを初期化しています。入力エリア2つとも値が入っていないと実行できないようにしています。
-
Value1,Value2が更新されるたびにコマンドのNotifyCanExecuteChangedを呼び出し、コマンドの実行可否を更新するようにしています。
-
using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Input;
namespace MVVMSample001.ViewModels
{
class MainWindowViewModel : ObservableObject
{
private string _value1;
/// <summary>
/// 入力値1
/// </summary>
public string Value1
{
get => _value1;
set
{
SetProperty(ref _value1, value);
// 実行可否を更新
CalculateCommand?.NotifyCanExecuteChanged();
}
}
private string _value2;
/// <summary>
/// 入力値2
/// </summary>
public string Value2
{
get => _value2;
set
{
SetProperty(ref _value2, value);
// 実行可否を更新
CalculateCommand?.NotifyCanExecuteChanged();
}
}
private string _result;
/// <summary>
/// 計算結果
/// </summary>
public string Result
{
get => _result;
set => SetProperty(ref _result, value);
}
/// <summary>
/// 計算コマンド
/// </summary>
public IRelayCommand CalculateCommand { get; }
/// <summary>
/// コンストラクタ
/// </summary>
public MainWindowViewModel()
{
CalculateCommand = new RelayCommand(
execute: () =>
{
try
{
Result = $"{Value1} + {Value2} = {int.Parse(Value1) + int.Parse(Value2)}";
}
catch
{
Result = "Error!";
}
},
canExecute: () =>
{
return !string.IsNullOrEmpty(Value1) && !string.IsNullOrEmpty(Value2);
});
}
}
}
9)Viewの作成
ViewModelの設定
MainWindow.Xamlを開き、
Windowに
xmlns:vm="clr-namespace:MVVMSample001.ViewModels"と
d:DataContext="{d:DesignInstance Type=vm:MainWindowViewModel, IsDesignTimeCreatable=True}"を追加します。
これでXAML編集時にViewModelのプロパティが参照できるようになります。
<Window
x:Class="MVVMSample001.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MVVMSample001.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:vm="clr-namespace:MVVMSample001.ViewModels"
Title="MainWindow"
Width="800"
Height="450"
d:DataContext="{d:DesignInstance Type=vm:MainWindowViewModel,
IsDesignTimeCreatable=True}"
ui:WindowHelper.UseModernWindowStyle="True"
mc:Ignorable="d">
<Grid>
</Grid>
</Window>
MainWindow.xaml.csを開き、
InitializeComponentの下に
DataContext = new MainWindowViewModel();を追加します。
using MVVMSample001.ViewModels;
using System.Windows;
namespace MVVMSample001.Views
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
}
画面作成
WindowにResizeMode="CanResizeWithGrip"を追加し、
Widthを350, Heightを300に編集します。
GridをStackPanelに変更し、
TextBox2つ、Button、TextBlockを追加します。
それぞれViewModelで定義したプロパティとバインディングします。
<Window
x:Class="MVVMSample001.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MVVMSample001.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:vm="clr-namespace:MVVMSample001.ViewModels"
Title="MainWindow"
Width="350"
Height="300"
d:DataContext="{d:DesignInstance Type=vm:MainWindowViewModel,
IsDesignTimeCreatable=True}"
ui:WindowHelper.UseModernWindowStyle="True"
ResizeMode="CanResizeWithGrip"
mc:Ignorable="d">
<StackPanel>
<!-- 入力数値 -->
<TextBox Margin="10" Text="{Binding Value1, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Margin="10" Text="{Binding Value2, UpdateSourceTrigger=PropertyChanged}" />
<!-- 計算ボタン -->
<Button
Margin="10"
Command="{Binding CalculateCommand}"
Content="Calculate" />
<!-- 計算結果 -->
<TextBlock Margin="10" Text="{Binding Result}" />
</StackPanel>
</Window>
完成です。

10)実行
実行してみます。
初期状態はCalculateボタンは無効化しています。

2つのテキストボックスに数値を入力するとボタンが有効化します。

ボタンを押すと計算結果が下に表示されます。

11)参考
12).NET 6.0の場合
12-1)概要
Visual Studio 2022と.NET 6で作ってみます。
ViewとViewModelはプロジェクトを分けます。
12-2)環境
- Windows 11 Version 21H2
- .NET 6
- Visual Studio 2022 Version 17.0.4
- WPF
- Microsoft.Toolkit.Mvvm Version 7.1.2
- ModernWpfUI Version 0.9.4
12-3)プロジェクトの作成
新規プロジェクトでWPF アプリケーションを選択します。
プロジェクト名にMVVMSample001v2と入力し、フレームワークに.NET 6.0を選択します。
12-4-a)プロジェクトの設定
プロジェクトをダブルクリックしてcsprojファイルを開きます。
TargetFrameworkの値をnet6.0-windows10.0.18362.0に編集します。
12-4-b)プロジェクトの追加
ソリューションに新しいプロジェクトクラスライブラリを追加します。

名前はMVVMSample001v2.VM、フレームワークは.NET 6.0にします。
12-5)NuGetパッケージ追加
以下のパッケージをNuGetで追加します。
-
MVVMSample001v2プロジェクトModernWpfUI
-
MVVMSample001v2.VMプロジェクトMicrosoft.Toolkit.Mvvm
12-6-a)ファイル準備
MVVMSample001v2プロジェクト
MainWindow.xamlを削除します。
プロジェクト直下にViewsフォルダを作成します。
Viewsフォルダにウィンドウ(WPF)MainWindow.xamlを追加します。
MVVMSample001v2.VMプロジェクト
Class1.csを削除します。
プロジェクト直下にクラスMainWindowViewModel.csを追加します。

12-6-b)プロジェクト参照設定
MVVMSample001v2プロジェクトの依存関係を右クリックし、プロジェクト参照の追加を選択します。
MVVMSample001v2.VMにチェックを入れOKします。
これで View → ViewModel の参照関係になります。

12-7)画面デザイン変更
App.xamlに
xmlns:ui="http://schemas.modernwpf.com/2019"を追加し、
StartupUriをViews/MainWindow.xamlに修正し、
Application.ResourcesにResourceDictionaryを追加します。
<Application x:Class="MVVMSample001v2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MVVMSample001v2"
xmlns:ui="http://schemas.modernwpf.com/2019"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ThemeResources />
<ui:XamlControlsResources />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
</ResourceDictionary>
</Application.Resources>
</Application>
MainWindow.xamlに
xmlns:ui="http://schemas.modernwpf.com/2019"と
ui:WindowHelper.UseModernWindowStyle="True"を追加します。
<Window x:Class="MVVMSample001v2.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MVVMSample001v2.Views"
xmlns:ui="http://schemas.modernwpf.com/2019"
ui:WindowHelper.UseModernWindowStyle="True"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
</Grid>
</Window>
12-8)ViewModelの作成
MainWindowViewModel.csを以下のように編集します。
using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Input;
namespace MVVMSample001v2.VM
{
public class MainWindowViewModel : ObservableObject
{
private string? _value1;
/// <summary>
/// 入力値1
/// </summary>
public string? Value1
{
get => _value1;
set
{
SetProperty(ref _value1, value);
// 実行可否を更新
CalculateCommand?.NotifyCanExecuteChanged();
}
}
private string? _value2;
/// <summary>
/// 入力値2
/// </summary>
public string? Value2
{
get => _value2;
set
{
SetProperty(ref _value2, value);
// 実行可否を更新
CalculateCommand?.NotifyCanExecuteChanged();
}
}
private string? _result;
/// <summary>
/// 計算結果
/// </summary>
public string? Result
{
get => _result;
set => SetProperty(ref _result, value);
}
/// <summary>
/// 計算コマンド
/// </summary>
public IRelayCommand CalculateCommand { get; }
/// <summary>
/// コンストラクタ
/// </summary>
public MainWindowViewModel()
{
CalculateCommand = new RelayCommand(
execute: () =>
{
try
{
Result = $"{Value1} + {Value2} = {int.Parse(Value1 ?? "") + int.Parse(Value2 ?? "")}";
}
catch
{
Result = "Error!";
}
},
canExecute: () =>
{
return !string.IsNullOrEmpty(Value1) && !string.IsNullOrEmpty(Value2);
});
}
}
}
12-9)Viewの作成
ViewModelの設定
MainWindow.Xamlを開き、
Windowに
xmlns:vm="clr-namespace:MVVMSample001v2.VM;assembly=MVVMSample001v2.VM"と
d:DataContext="{d:DesignInstance Type=vm:MainWindowViewModel, IsDesignTimeCreatable=True}"を追加します。
MainWindow.xaml.csを開き、
InitializeComponentの下に
DataContext = new VM.MainWindowViewModel();を追加します。
画面作成
WindowにResizeMode="CanResizeWithGrip"を追加し、
Widthを350, Heightを300に編集します。
Gridの部分を以下のように書きかえます。
<StackPanel>
<!-- 入力数値 -->
<TextBox Margin="10"
Text="{Binding Value1,Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Margin="10"
Text="{Binding Value2,Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" />
<!-- 計算ボタン -->
<Button Margin="10"
Command="{Binding CalculateCommand}"
Content="Calculate" />
<!-- 計算結果 -->
<TextBlock Margin="10"
Text="{Binding Result,Mode=OneWay}" />
</StackPanel>
Discussion