WPFを使う-ViewModelとModel(1)

WPFのView – View Model – Modelでのパラメータ設定時のViewからのパラメータの設定とエラーチェック方法の一つの考え方。

初めに

Modelないにある動作パラメータをViewから設定させる場合、このMSDNの記事によると、大体次の2つのパターンが考えられるそうだ。

  1. View Modelラッパーからモデルインスタンスを直接公開し、モデルクラスにINotifyPropertyChangedインターフェイスを実装させる。
  2. WPF, .NET Framework 4の動的オブジェクトおよび動的メソッドディスパッチを使用する。

モデルインスタンスを直接公開した場合、モデル側にINotifyPropertyChangedインターフェースを用意するのはまあいいとして、エラーチェックのためINotifyDataErrorInfoなどのエラー通知のインターフェースをも用意するのはちょっと違うのかなと思った。

そこで、次のような実装にし、パラメータの設定とチェックに関して、モデル側にはINotifyPropertyChanged, INotifyDataErrorInfoがなくてもいいような構造にしてみた。

基本的な考え方

モデルインスタンスは直接公開せず、ただ2の対応を行った場合も、型変換に難があったため、ViewModelに設定用のラッパーメソッドを設けた。

ただし、パラメータのチェックはモデル側で行い、そのチェック処理とエラー内容を表す情報はメソッドで返すようにする。

クラス構造

構造自体は特に代わり映えもない。Prismを使用しているため、ViewModelの親はBindableBaseを使用している。

PlantUML Syntax:</p>
<p>BindableBase<|–ViewModel</p>
<p>INotifyDataErrorInfo<|–ViewModel</p>
<p>ViewModel o– Model</p>
<p>class ViewModel {</p>
<p>string parameter</p>
<p>ErrorsContainer<string> ErrorsContainer</p>
<p>bool HasErrors()</p>
<p>IEnumerable GetErrors()</p>
<p>}</p>
<p>class Model {</p>
<p>string parameter</p>
<p>Dictionary<string, string> isValid()</p>
<p>}</p>
<p>

クラスメソッド

Modelには、パラメータチェック用のisValidメソッドを設ける。このメソッドでは、モデル内のパラメータをすべてチェックし、エラーがあった場合、メンバ名とエラーを返すようにしている。

ViewModelには、ラッパー用のparameterアクセサを設ける。

パラメータアクセス時の処理方法(シーケンス)

PlantUML Syntax:</p>
<p>actor user</p>
<p>user -> View : Data Input</p>
<p>View -> ViewModel :parameter set</p>
<p>ViewModel -> Model : Search parameter</p>
<p>ViewModel -> Model : Change parameter</p>
<p>ViewModel -> ViewModel :RaisePropertyChanged</p>
<p>ViewModel -> Model : isValid</p>
<p>alt !isValid</p>
<p>ViewModel -> ViewModel : Set Error</p>
<p>ViewModel -> ViewModel :RaisePropertyChanged(error member)</p>
<p>end</p>
<p>

実装


class Model {
	public int parameter { set; get; }
	/// <summary>
	/// 設定内容の値チェック
	/// </summary>
	/// <returns>属性名とエラーメッセージのペアを返す</returns>
	public Dictionary<string, string> isValid() {
		var ret = new Dictionary<string, string>();
		parameterのチェック
		if(エラー) {
			ret[nameof(parameter)] = エラーメッセージ;
		}
		return ret;
	}
}
class ViewModel 
public class FileParameterViewModel : BindableBase, INotifyDataErrorInfo {
	Model model = new Model();

	private ErrorsContainer<string> ErrorsContainer { get; }
	public bool HasErrors {
		get => ErrorsContainer.HasErrors;
	}
	public IEnumerable GetErrors(string propertyName) {
		return this.ErrorsContainer.GetErrors(propertyName);
	}
	/// <summary>
	/// パラメータのチェックを行い、エラーがある場合、エラーコンテナに詰め込む
	/// </summary>
	private void check() {
		var result = parameter_.isValid();
		ErrorsContainer.ClearErrors();
		foreach(var data in result) {
			List<string> work = new List<string>();
			work.Add(data.Value);
			ErrorsContainer.SetErrors(data.Key, work);
			RaisePropertyChanged(data.Key);
		}
	}
	/// <summary>
	/// データの設定用メソッド
	/// </summary>
	private void setProperty<T>(T value, [CallerMemberName] string propertyName = null) where T : IComparable {
		PropertyInfo property = parameter_.GetType().GetProperty(propertyName);
		if (property == null || (!property.CanRead && !property.CanWrite)) {
			return;
		}
		var target = (property.GetValue(parameter_)) as IComparable;
		if (target != null && value.CompareTo(target) != 0) {
			property.SetValue(parameter_, value);
			RaisePropertyChanged(propertyName);
			check();
		}
	}

	public int parameter {
		set => setProperty(value);
		get => model.parameter;
	}
}

これで、ViewModelのparameterをViewにバインドすることで、もしエラーがあった場合、View側に表示される。

最後に

WPFを使ったプログラムを作ろうと思ったら、どんどん深みにはまっていっている。特にMVVMは今まで利用したこともない考え方だったので、何が正解なのか、不正解なのか、やっていいこと、悪いことが全く分からず、五里霧中な状態。

一応、何かちょっとだけ、見えてきたような感じ。

コメント

タイトルとURLをコピーしました