📟

WPF MVVMサンプル(足し算)

2021/06/28に公開

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

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"を追加し、
StartupUriViews/MainWindow.xamlに修正し、
Application.ResourcesResourceDictionaryを追加します。

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

MainWindow.xaml
<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を以下のように編集します。

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

画面作成

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

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