メインコンテンツまでスキップ

MVVMモデルでWPFプログラムを作ってみる

土井
ディアシステム(株)開発一部第2課

こんにちは。開発 1 部2課の土井です。
今回は WPF を MVVM モデル(Model-View-ViewModel)を使って、作ってみたいと思います。
自分自身、このモデルを使いながら作ることもあるのですが、作りながらこれってどんな仕組みで動いてるんだっけ?とか思うこともあり、ここで一旦内容をまとめたいと思います。

動作環境

  • Visual Studio Community 2022
  • .NET8.0

まずそもそも MVVM モデル(Model-View-VIewModel)とは?
アプリケーションを作る際に、Model-View-VIewModel の三つに分けて整理する考え方です。それぞれの役割を分割させることで、作りやすくて、直しやすいアプリができます。

  • Model(モデル)

データや計算など、アプリの中身の部分です。
例えば、「売上データを計算する」「ファイルから情報を読む」などを行う部分です。

  • View(ビュー)

ユーザーが見る画面の部分です。
こちらは XAML で作成されます。

  • ViewModel(ビューモデル)

View と Model の繋ぎ役です。
View からの操作(ボタンを押すなど)を受け取って、Model に伝えたり、Model からのデータを View に渡したりします。

MVVM モデルのメリット

  1. 画面と処理を分けられる → 直すときに楽!
  2. テストしやすい → 画面を動かさなくても、処理だけ確認できる!
  3. チームで作りやすい → 役割が分かれているから、分担しやすい!

では、早速実際のプログラムではどのようになるのか、お見せしようと思います。
「画面のボタンを押すと、テキストが変わる」というシンプルなものを作ってみます。

次のような構成で作成します。

画像1

  • View
MainWindow.Xaml
<Window x:Class="MvvmPractice.View.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:MvvmPractice.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">

<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>

<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="{Binding Message}" FontSize="10" Margin="10"/>
<Button Content="メッセージ表示" Command="{Binding ShowMessageCommand}" FontSize="10" />
</StackPanel>

</Window>

「Window.DataContext」は View と ViewModel を紐づけるための部分です。
「local:MainViewModel」と記載していますが、これは Window タグ内の xmlns:local の部分で、ViewModel までの名前空間を指定してあげる必要があります。
また、今回のプログラムでは XAML 内で記載していますが、MainWindow.xaml.cs 内で ViewModel を紐づけることも可能です。

TextBlock タグの「Binding Message」と書かれている部分ですが、これは「ViewModel の”Message”って名前のプロパティと紐づけるよー!」といった感じです。よくバインディングすると言ったりもします。なので、ここには ViewModel 側にある、Message プロパティの値が表示されます。

  • ViewModel
MainViewModel.cs
using MvvmPractice.Model;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace MvvmPractice.ViewModel
{
public class MainViewModel : INotifyPropertyChanged
{
private string _message;
private MessageModel _model;

public string Message
{
get => _message;
set
{
_message = value;
OnPropertyChanged(nameof(Message));
}
}

public ICommand ShowMessageCommand { get; }

public MainViewModel()
{
_model = new MessageModel();
ShowMessageCommand = new RelayCommand(ShowMessage);
}

private void ShowMessage()
{
Message = _model.GetMessage();
}

public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

}
}

これが ViewModel の中身ですが、知らないものが多くあるかと思いますので、一つずつ解説していきます。

  • INotifyPropertyChanged

View にデータが変わったことを教えてくれる機能を持ったインターフェースです。今回だとこのクラスの「Message」のプロパティの値が変わった時に、View へとそのことを教える役割があります。WPF で MVVM モデルを使うときは、ほぼ必ず使うインターフェースです。

  • ICommand

UI 操作を ViewModel に伝えるためのインターフェースです。
今回で言うと、View 側の button タグ内で「Command="{Binding ShowMessageCommand}"」と記載し、ViewModel 側と紐づけています。

  • PropertyChangedEventHandler PropertyChanged

これは INotifyPropertyChanged インターフェースの中で定義されているイベントです。
INotifyPropertyChanged インターフェースを使う際には必ず、このイベントを実装する必要があります。
プロパティの値が変わったときは、この PropertyChanged を通じて View へと通知する必要があります。

  • protected void OnPropertyChanged(string name) =>
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

ここが少し複雑ですが、一つ一つご説明します。

「=>」は式形式メソッドと呼ばれる記法です。
これを通常の構文に直すと以下のようになります。

protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

PropertyChanged?は null 条件演算子と呼ばれるものです。この PropertyChanged が null でない場合だけ呼び出すということができます。

Invoke は「このイベントに登録されているメソッドを実行する」という意味です。
今回で言うと、

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

・PropertyChanged イベントに登録されているメソッドがあれば
・this(通知元)と PropertyChangedEventArgs(names)(変更されたプロパティ名)を渡して、
・そのメソッドを実行する(=通知する) という処理になります。

PropertyChangedEventArgs は PropertyChanged イベントが発火されたときに、「どのプロパティが変更されたか」を伝えるための情報を持つ PropertyChanged イベント専用の引数クラスです。引数に変更されたプロパティ名が入ります。

RelayCommand.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace MvvmPractice.ViewModel
{
public class RelayCommand : ICommand
{
private readonly Action _execute;

public RelayCommand(Action execute) => _execute = execute;

public event EventHandler CanExecuteChanged;

public bool CanExecute(object parameter) => true;

public void Execute(object parameter) => _execute();
}

}

こちらはボタンなどの操作を ViewModel に簡単に伝えるためのクラスです。
CanExecute、Execute、CanExecuteChanged はこのインターフェースを使うときには実装する必要があります。

  • CanExecute このコマンドが実行可能かどうかを返すメソッドです。
    true を返せば、ボタンなどの UI 要素は有効になります。

  • Execute 実際の処理を実行するメソッドです。
    parameter には、XAML から渡された値が入ります。

  • CanExecuteChanged CanExecute の結果が変わったときに通知するイベントです。

先ほどの MainViewModel.cs に戻ると次のように書かれている箇所があるかと思います。

ShowMessageCommand = new RelayCommand(ShowMessage);

ここで Model 内で行っている処理を ViewModel のプロパティと紐づけています。
ShowMessageCommand は View の button タグ内でバインディングされているので、ボタンを押すと ShowMessageCommand が呼び出されて、ShowMessage メソッドが実行されるといった動きになります。

  • Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MvvmPractice.Model
{
public class MessageModel
{
public string GetMessage()
{
return "腹筋6LDKかい!";
}

}
}

こうして画面を起動して動かしてみましょう。

この画面でボタンを押すと、

画像2

こうなります。

画像3

実際にこの MVVM モデルを使用するときですが、INotifyPropertyChanged と ICommand を継承した ViewModel の Base のようなインターフェースを用意しておいて、ViewModel のクラスではそれを継承して使っていました。そうすることで ViewModel を作成するたびにインターフェースを継承するたびに、メソッドを実装しなくても良くなり、ViewModel 全体として共通する処理があれば、その中に記述できます。

このような形で MVVM モデルというものが活用できます。
ちなみにこの MVVM モデルでは画面とロジックを分離するという目的があるため、View のコードビハインド(MainWindow.xaml.cs など)には基本的には何も書かないという考えがあるのですが、新しい画面を別ウィンドウで開きたいときや、画面を閉じたいときなんかはどうしてもコードビハインドで実装しないといけなかったりします。(いいやり方あれば教えてほしい)
そこらへんがちょっと不便で、プログラムが複雑になったりもするので注意が必要です。

未経験から始める
システムエンジニア

一生モノのITスキルを身につけよう

あなたの経験とスキルを
ディアシステムで発揮してください!