はじめに
この記事では以下の記事でコマンドの確認をしたアプリケーションをMVVMパターンライクなものに置き換えてみます。
MVVMパターンライクと書いたのはModelが必要なかったためです。View、ViewModelおよびCommandを実装します。
今回はRoutedUICommandクラスとCommandBindingクラスは使わず、ICommandインターフェースを実装することにします。
開発環境は以下のとおりです。
オペレーティングシステム | Windows10 x64 |
Visual Studio | Microsoft Visual Studio Community 2019 Version 16.11.2 |
.NET Framewrok | 4.7.2 |
アプリケーションの仕様
まず起動時の画面は以下のとおりです。コピーボタンと貼り付けボタンは無効化されています。

コピーボタンの隣のテキストボックスに文字を入力するとコピーボタンが有効化されるようにします。

コピーボタンを押すと貼り付けボタンが有効化されるようにします。

貼り付けボタンを押すと右側のテキストブロックにコピーした文字列を表示します。貼り付けが終わると貼り付けボタンは再度無効化されるようにします。

リセットボタンを押すと起動時の画面に戻るようにします。

プロジェクトのフォルダ構成とファイル
WPFアプリケーションのプロジェクト作成したら、プロジェクト内にViewsフォルダ、ViewModelsフォルダ、およびCommandsフォルダを作成します。
フォルダの作成手順 「プロジェクトを右クリック -> 追加 -> 新しいフォルダー」
自動的に生成されるMainWindow.xamlファイルは削除します。
そして下図のように各フォルダに必要なファイルを作成します。

①Commandsフォルダ
- CopyCommand.cs
- PasteCommans.cs
- ResetCommand.cs
作成手順 「Commandsフォルダを右クリック -> 追加 -> クラス」
②ViewModelsフォルダ
- MainWindowViewModel.cs
作成手順 「ViewModelsフォルダを右クリック -> 追加 -> クラス」
③Viewsフォルダ
- MainWindow.xaml
作成手順 「Viewsフォルダを右クリック -> 追加 -> ウィンドウ(WPF)」
Viewを起動するためにApp.xamlファイルのStartupUri属性の値を以下のとおり変更してください。
<Application x:Class="WpfMVVMSample.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMVVMSample"
StartupUri="Views\MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
コマンドの定義
3つのコマンドの定義は以下のとおりです。ICommandインターフェースのメンバーを実装しています。
public interface ICommand
{
bool CanExecute(object parameter);
void Execute(object parameter);
event EventHandler CanExecuteChanged;
}
ResetCommand.csは以下のとおりです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using WpfMVVMSample.ViewModels;
namespace WpfMVVMSample.Commands
{
class ResetCommand : ICommand
{
MainWindowViewModel _vm;
public ResetCommand(MainWindowViewModel vm)
{
_vm = vm;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
// 常に有効化
return true;
}
public void Execute(object parameter)
{
// リセット処理をする
Clipboard.Clear();
_vm.CopyText = string.Empty;
_vm.PasteText = string.Empty;
}
}
}
コンストラクタでViewModelのインスタンスを取得しています。
リセットボタンは常に有効化しておくためCanExecuteメソッドはtrueを返すようにしています。
CanExecuteChangedイベントの定義は定石のようです。
CopyCommand.csとPasteCommand.csは以下のとおりです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows;
using WpfMVVMSample.ViewModels;
namespace WpfMVVMSample.Commands
{
class CopyCommand : ICommand
{
MainWindowViewModel _vm;
public CopyCommand(MainWindowViewModel vm)
{
_vm = vm;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
if (_vm.CopyText == null)
{
return false;
}
// テキストボックスに空白以外の文字列が入力されていた場合はコピーボタンを有効化する
return !string.IsNullOrWhiteSpace(_vm.CopyText);
}
public void Execute(object parameter)
{
string text = _vm.CopyText;
if (!string.IsNullOrWhiteSpace(text))
{
// クリップボードにテキストボックスの文字列を保存する
Clipboard.SetData(DataFormats.Text, text);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows;
using WpfMVVMSample.ViewModels;
namespace WpfMVVMSample.Commands
{
class PasteCommand : ICommand
{
MainWindowViewModel _vm;
public PasteCommand(MainWindowViewModel vm)
{
_vm = vm;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
string text = Clipboard.GetText();
// クリップボードにテキストが保存されていた場合は貼り付けボタンを有効化する
return !string.IsNullOrWhiteSpace(text);
}
public void Execute(object parameter)
{
string text = Clipboard.GetText();
_vm.PasteText = text;
// テキストブロックに貼り付けた後はクリップボードをクリアする
// クリップボードが空なので貼り付けボタンは無効化される
Clipboard.Clear();
}
}
}
CopyCommand.csとPasteCommand.csは特に問題は無いでしょう。
ここでもCanExecuteChangedイベントの定義は定石のようです。
ViewModelの定義
ViewModelの定義は以下のとおりです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using WpfMVVMSample.Commands;
namespace WpfMVVMSample.ViewModels
{
class MainWindowViewModel : INotifyPropertyChanged
{
ICommand _resetCommand, _copyCommand, _pasteCommand;
public MainWindowViewModel()
{
_resetCommand = new ResetCommand(this);
_copyCommand = new CopyCommand(this);
_pasteCommand = new PasteCommand(this);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
string _copyText;
string _pasteText;
public string CopyText
{
get { return _copyText; }
set
{
if(value != _copyText)
{
_copyText = value;
NotifyPropertyChanged();
}
}
}
public string PasteText
{
get { return _pasteText; }
set
{
if (value != _pasteText)
{
_pasteText = value;
NotifyPropertyChanged();
}
}
}
public ICommand ResetCommand
{
get { return _resetCommand; }
}
public ICommand CopyCommand
{
get { return _copyCommand; }
}
public ICommand PasteCommand
{
get { return _pasteCommand; }
}
}
}
コンストラクタでは3つのコマンドのインスタンスを生成しています。
またViewの中で各コマンドをバインドするためICommand型のプロパティとして公開しています。
Viewの定義
Viewの画面とコードビハインドは以下のとおりです。
<Window x:Class="WpfMVVMSample.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:WpfMVVMSample.Views"
mc:Ignorable="d"
Title="Basic MVVM Sample" Height="160" Width="400">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="6">
<Button Command="{Binding CopyCommand}" Content="コピー" Width="60" Margin="6"/>
<TextBox Text="{Binding CopyText, UpdateSourceTrigger=PropertyChanged}" Width="240" Height="30" VerticalContentAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="6">
<Button Command="{Binding PasteCommand}" Content="貼り付け" Width="60" Margin="6"/>
<TextBlock Text="{Binding PasteText}" Width="240" Height="30" />
</StackPanel>
<Button Command="{Binding ResetCommand}" Grid.Row="2" Content="リセット" Width="60" Margin="6"/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace WpfMVVMSample.Views
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Clipboard.Clear();
DataContext = new WpfMVVMSample.ViewModels.MainWindowViewModel();
}
}
}
コンストラクタの中ではDataContextにViewModelのインスタンスを設定しています。
繰り返しになりますが、各コマンドをXAMLファイルの中でバインドできるようにするためです。
以上の実装で仕様どおりの動作をすることが確認できます。
MVVMパターンの各パーツの関係性は下図のとおりです。

今回はMVVMパターンライクな実装を検証しました。
なお、本アプリケーションではコピー、貼り付け、およびリセットの各コマンド用のファイルを3つに分けましたが、コマンドパラメータを使用すれば1つのファイルで処理を振り分けることも可能です。そのような実装例については以下の記事をご確認ください。
以上です。
コメント