Edited at

WPF用 ViewModelの基底クラスサンプル

More than 1 year has passed since last update.

WPFでアプリケーション開発する場合、MVVMパターンを利用しての開発が推奨されています。アプリケーションを Model, View, ViewModel の3つの層に分割して開発する手法です。

WPFではMVVMパターンを用いて開発するためにデータバインドとかいろいろ機能が用意されているのですが、それらを利用するためには幾つかのお約束があって、ViewModelではINotifyPropertyChangedIDataErrorInfoという2つのインターフェイスを実装することが求めらます(別に必須ではないですが)。また、ViewからViewModelへの通知を受け取る場合、ICommandインターフェイスを介して行われるため、この実装も必要となります。

これらの実装はほぼ定形ですので、ViewModelの基底クラスとして作成しておくのが普通です。最低限の機能を実装した基底クラスを以下に示します。


ViewModelの基底クラスのサンプル

INotifyPropertyChangedIDataErrorInfoの実装、及び、ICommandを実装するためのヘルパークラスを実装しています。


ViewModelBase.cs

using System;

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

namespace WpfTest
{
#region ** Class : ViewModelBase
/// <summary>
/// ViewModelの基底クラス
/// INotifyPropertyChanged と IDataErrorInfo を実装する
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged, IDataErrorInfo
{
#region == implement of INotifyPropertyChanged ==

// INotifyPropertyChanged.PropertyChanged の実装
public event PropertyChangedEventHandler PropertyChanged;

// INotifyPropertyChanged.PropertyChangedイベントを発生させる。
protected virtual void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

#endregion

#region == implemnt of IDataErrorInfo ==

// IDataErrorInfo用のエラーメッセージを保持する辞書
private Dictionary<string, string> _ErrorMessages = new Dictionary<string, string>();

// IDataErrorInfo.Error の実装
string IDataErrorInfo.Error
{
get { return (_ErrorMessages.Count > 0) ? "Has Error" : null; }
}

// IDataErrorInfo.Item の実装
string IDataErrorInfo.this[string columnName]
{
get
{
if (_ErrorMessages.ContainsKey(columnName))
return _ErrorMessages[columnName];
else
return null;
}
}

// エラーメッセージのセット
protected void SetError(string propertyName, string errorMessage)
{
_ErrorMessages[propertyName] = errorMessage;
}

// エラーメッセージのクリア
protected void ClearErrror(string propertyName)
{
if (_ErrorMessages.ContainsKey(propertyName))
_ErrorMessages.Remove(propertyName);
}

#endregion

#region == implemnt of ICommand Helper ==

#region ** Class : _DelegateCommand
// ICommand実装用のヘルパークラス
private class _DelegateCommand : ICommand
{
private Action<object> _Command; // コマンド本体
private Func<object, bool> _CanExecute; // 実行可否

// コンストラクタ
public _DelegateCommand(Action<object> command, Func<object, bool> canExecute = null)
{
if (command == null)
throw new ArgumentNullException();

_Command = command;
_CanExecute = canExecute;
}

// ICommand.Executeの実装
void ICommand.Execute(object parameter)
{
_Command(parameter);
}

// ICommand.Executeの実装
bool ICommand.CanExecute(object parameter)
{
if (_CanExecute != null)
return _CanExecute(parameter);
else
return true;
}

// ICommand.CanExecuteChanged の実装
event EventHandler ICommand.CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
#endregion

// コマンドの生成
protected ICommand CreateCommand(Action<object> command, Func<object, bool> canExecute = null)
{
return new _DelegateCommand(command, canExecute);
}

#endregion
}
#endregion
}



上記コードを利用したサンプル


ViewModel.cs

using System;

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

namespace WpfTest
{
#region ** Class : ViewModel
/// <summary>
/// ViewModel
/// </summary>
class ViewModel : ViewModelBase
{
// プロパティの値を保持するメンバ変数
private int _property1 = 0;

// プロパティ
public int Property1
{
get { return _property1; }
set
{
// 値が変更されたら、INotifyPropertyChanged.PropertyChanged を発生させる。
if (_property1 != value)
{
_property1 = value;

// 値は0以上、10以下。それ以外はエラー
if (_property1 >= 0 && _property1 <= 10)
ClearErrror("Property1");
else
SetError("Property1", "Error: argument out of range");

RaisePropertyChanged("Property1");

}
}
}

public ICommand Add1Command { get; private set; }

// コンストラクタ
public ViewModel()
{
Property1 = 0;
Add1Command = CreateCommand(v => { Property1 = Property1 + 1; });
}
}
#endregion
}



MainWindow.xaml

<Window x:Class="WpfTest.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:ViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource ViewModel}">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="数値を入力してください:" FontSize="14" Margin="10" />
<TextBox Text="{Binding Property1, ValidatesOnDataErrors=True, Mode=TwoWay}" FontSize="14" Width="50" Margin="10" HorizontalContentAlignment="Right" />
</StackPanel>
<Button Grid.Row="1" Content="1を加算" FontSize="14" Padding="20,10,20,10" VerticalAlignment="Center" HorizontalAlignment="Center" Command="{Binding Add1Command}" />
</Grid>
</Window>


MainWindow.xaml.cs

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.Navigation;
using System.Windows.Shapes;

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