MVVMモデルでWPFプログラムを作ってみる
こんにちは。開発 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 モデルのメリット
- 画面と処理を分けられる → 直すときに楽!
- テストしやすい → 画面を動かさなくても、処理だけ確認できる!
- チームで作りやすい → 役割が分かれているから、分担しやすい!
では、早速実際のプログラムではどのようになるのか、お見せしようと思います。
「画面のボタンを押すと、テキストが変わる」というシンプルなものを作ってみます。
次のような構成で作成します。

- View
<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
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 イベント専用の引数クラスです。引数に変更されたプロパティ名が入ります。
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かい!";
}
}
}
こうして画面を起動して動かしてみましょう。
この画面でボタンを押すと、
こうなります。

実際にこの MVVM モデルを使用するときですが、INotifyPropertyChanged と ICommand を継承した ViewModel の Base のようなインターフェースを用意しておいて、ViewModel のクラスではそれを継承して使っていました。そうすることで ViewModel を作成するたびにインターフェースを継承するたびに、メソッドを実装しなくても良くなり、ViewModel 全体として共通する処理があれば、その中に記述できます。
このような形で MVVM モデルというものが活用できます。
ちなみにこの MVVM モデルでは画面とロジックを分離するという目的があるため、View のコードビハインド(MainWindow.xaml.cs など)には基本的には何も書かないという考えがあるのですが、新しい画面を別ウィンドウで開きたいときや、画面を閉じたいときなんかはどうしてもコードビハインドで実装しないといけなかったりします。(いいやり方あれば教えてほしい)
そこらへんがちょっと不便で、プログラムが複雑になったりもするので注意が必要です。