先日書いた記事で、めんどくさいと思ってた部分を簡単にかけるようにしました。
MVVMでめんどくさいと思ってる部分を、個人的にどうやって緩和してるか - かずきのBlog@hatena
データのバリデーションンを伴うMとVMのプロパティの同期は以下のように書く必要がありました。
publicclass PersonViewModel { public ReactiveProperty<string> Name { get; private set; } public ReactiveProperty<string> Age { get; private set; } public PersonViewModel(Person model) { this.Name = model // M -> VM .ObserveProperty(x => x.Name) // IObservable<string>に変換して .ToReactiveProperty() // ReactiveProperty<string>に変換して .SetValidateNotifyError(x => // 空文字の時はエラーにするstring.IsNullOrWhiteSpace(x) ? "Name is required" : null); this.Name // VM -> M .Where(_ => !this.Name.HasErrors) // エラーが無いときは .Subscribe(x => model.Name = x); // 値を書き戻すthis.Age = model // M -> VM .ObserveProperty(x => x.Age) // IObservable<int>に変換して .Select(x => x.ToString()) // IObservable<string>に変換して .ToReactiveProperty() // ReactiveProperty<string>に変換して .SetValidateNotifyError(x => // int型に変換できない場合はエラーにする { int result; // no usereturnint.TryParse(x, out result) ? null : ""; }); this.Age // VM -> M .Where(_ => !this.Age.HasErrors) // エラーが無いときは .Select(x => int.Parse(x)) // int型に変換して .Subscribe(x => model.Age = x); // 書き戻す } }
これを、ToReactivePropertyAsSynchronizedメソッドにバリデーションエラーのときは値を無視するようにするオプションを追加して以下のようにかけるようにしました。
publicclass PersonViewModel { public ReactiveProperty<string> Name { get; private set; } public ReactiveProperty<string> Age { get; private set; } public PersonViewModel(Person model) { this.Name = model .ToReactivePropertyAsSynchronized( x => x.Name, ignoreValidationErrorValue:true) .SetValidateNotifyError(x => string.IsNullOrEmpty(x) ? "error": null); this.Age = model .ToReactivePropertyAsSynchronized( x => x.Age, convert: x => x.ToString(), convertBack: x => int.Parse(x), ignoreValidationErrorValue:true) .SetValidateNotifyError(x => { int result; // no usereturnint.TryParse(x, out result) ? null : "error"; }); } }
今まではバリデーションエラーが、あろうが無かろうが、Mへ値が渡されてたのですが(しかも、convertBackに変な値がわたって例外がでると死ぬ)バリデーションエラーのときは値を流さないようにしました。個人的に気に入ってる。
動作は以下のようになります。
var model = new Person { Name = "tanaka", Age = 30 }; var vm = new PersonViewModel(model); Console.WriteLine("{0} {1}", model.Name, model.Age); // tanaka 30 vm.Age.Value = "50"; // valid value Console.WriteLine("{0} {1}", model.Name, model.Age); // tanaka 50 vm.Age.Value = "XX"; // invalid value Console.WriteLine("{0} {1}", model.Name, model.Age); // tanaka 50 vm.Age.Value = "30"; // valid value Console.WriteLine("{0} {1}", model.Name, model.Age); // tanaka 30