📟

WPF MVVMサンプル(足し算)

10 min read

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.Mvvm
  • ModernWpfUI

6)ファイル準備

MainWindow.xamlを削除します。

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

7)画面デザイン変更

App.xaml
xmlns:ui="http://schemas.modernwpf.com/2019"を追加し、
StartupUriViews/MainWindow.xamlに修正し、
Application.ResourcesResourceDictionaryを追加します。

App.xaml
<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"を追加します。

MainWindow.xaml
<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を呼び出し、コマンドの実行可否を更新するようにしています。
MainWindowViewModel.cs
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のプロパティが参照できるようになります。

MainWindow.xaml
<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();を追加します。

MainWindow.xaml.cs
using MVVMSample001.ViewModels;
using System.Windows;

namespace MVVMSample001.Views
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowViewModel();
        }
    }
}

画面作成

WindowResizeMode="CanResizeWithGrip"を追加し、
Width350, Height300に編集します。

GridStackPanelに変更し、
TextBox2つ、ButtonTextBlockを追加します。
それぞれViewModelで定義したプロパティとバインディングします。

MainWindow.xaml
<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)参考

https://github.com/emu2021makuake/MVVMSample001