MVVMã®ããã©ãããã¨æãã¦ãã¨ãã
Modelã¨ViewModelã®ã¯ã©ã¹ã®ãããã³ã°
MVVMã§ã¢ããªçµãã§ãã¨Modelã¨ViewModelã§ä¼¼ãæ§é ã®ã¯ã©ã¹ãä½ã£ã¦ãå¤ã®ç§»ãæ¿ããè¡ããã¨ãããã¾ããAutoMapperã¨ã使ã£ã¦ãããã®ã§ãããReactivePropertyã使ããã¨ã§ã楽ããããã¨ãã§ãã¾ãã
以ä¸ã®ãããªModelã¯ã©ã¹ãããã¨ãã¾ããï¼BindableBaseã¯ã©ã¹ã¯Prismã®INotifyPropertyChangedãå®è£ ããã¯ã©ã¹ã§ãï¼
public class Person : BindableBase { private string name; public string Name { get { return this.name; } set { this.SetProperty(ref this.name, value); } } private int age; public int Age { get { return this.age; } set { this.SetProperty(ref this.age, value); } } }
ä¸è¨ã®ã¯ã©ã¹ã¯2ããããã£ãããªãã§ããããããªæãã§INotifyPropertyChangedã®å¤æ´éç¥ã«å¯¾å¿ããããããã£ããããããã£ã¦ããã¯ã©ã¹ãModelã®ä¸ã«ã¯ããã¾ãã
ModelããViewModelã¸ã®å¤ã®ç§»ãæ¿ãã¯ãCodeplex.Reactive.Extensionsåå空éã«å®ç¾©ããã¦ããObservePropertyæ¡å¼µã¡ã½ããã使ããã¨ã§ç°¡åã«å®ç¾©ã§ãã¾ãã
public class PersonViewModel { public ReactiveProperty<string> Name { get; private set; } public ReactiveProperty<string> Age { get; private set; } public PersonViewModel(Person model) { this.Name = model .ObserveProperty(x => x.Name) // IObservable<string>ã«å¤æ .ToReactiveProperty(); // ReactiveProperty<string>ã«å¤æ this.Age = model .ObserveProperty(x => x.Age) // IObservable<int>ã«å¤æ .Select(x => x.ToString()) // IObservable<string>ã«å¤æ .ToReactiveProperty(); // ReactiveProperty<string>ã«å¤æ } }
ããã§ãPersonã¯ã©ã¹ã®ããããã£ãæ¸ãæããã¨ãViewModelã®ããããã£ãæ¸ãæããå¦çãå®ç¾©ã§ãã¾ãã
var p = new Person { Name = "tanaka" }; var vm = new PersonViewModel(p); Console.WriteLine(vm.Name.Value); // tanaka p.Name = "kimura"; Console.WriteLine(vm.Name.Value); // kimura p.Age = 10; Console.WriteLine(vm.Age.Value); // 10
Model â ViewModeléã®ä¸æ¹åãã¼ã¿ãã¤ã³ãã£ã³ã°ã¨è¦ããã¨ãã§ãã¾ãã
Modelã¨ViewModelã®åæ¹åãã¤ã³ãã£ã³ã°
åç´ãªã±ã¼ã¹ã§ã¯ãMã¨VMéã®ããããã£ãåæ¹åã§ãã¤ã³ãããToReactivePropertyAsSynchronizedã¡ã½ãããæä¾ãã¦ãã¾ãã
public class 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); // ReactiveProperty<string>ã«å¤æ this.Age = model .ToReactivePropertyAsSynchronized( x => x.Age, convert: x => x.ToString(), // M -> VMã®å¤æå¦ç convertBack: x => // VM -> Mã®å¤æå¦ç { try { return int.Parse(x); } catch { return -1; // error } }); } }
var p = new Person { Name = "tanaka", Age = 10 }; var vm = new PersonViewModel(p); Console.WriteLine("{0} {1}", vm.Name.Value, vm.Age.Value); // tanaka 10 vm.Name.Value = "kimura"; vm.Age.Value = "30"; Console.WriteLine("{0} {1}", p.Name, p.Age); // kimura 30 vm.Age.Value = "xxx"; // error!! Console.WriteLine("{0} {1}", p.Name, p.Age); // kimura -1
ããªãã¼ã·ã§ã³ã¨ã®é£æº
ToReactivePropertyAsSynchronizedæ¡å¼µã¡ã½ããã¯ãã·ã³ãã«ãªåæ¹åã®ãã¤ã³ãã£ã³ã°ããµãã¼ããã¦ãã¾ãããã¨ã©ã¼ããã£ããModelã®ãã¼ã¿ãæ¸ãæããããªãã¨ããè¦æã«ã¯å¯¾å¿ãã¦ãã¾ãããããã¯ãå°ãããã©ãããã§ãããèªåã§å¯¾å¿ããããç¡ãã§ãã
ã¾ããé ã追ã£ã¦ReactivePropertyã®å¤ã®æ¤è¨¼ãã説æãã¾ããReactivePropertyã®SetValidateNotifyErrorã¡ã½ããã§ReactivePropertyã®å¤ã®æ¤è¨¼ãåºæ¥ã¾ããï¼ãã®ã»ãã«ãè²ã æä¾ãã¦ãã¾ããä¸çªåç´ãªå¥´ã§ï¼ã¨ã©ã¼ãããå ´åã¯ã¨ã©ã¼ã¡ãã»ã¼ã¸ãè¿ãã¦ãã¨ã©ã¼ããªãå ´åã¯nullãè¿ãããã«ãã¾ãã
public class PersonViewModel { public ReactiveProperty<string> Name { get; private set; } public ReactiveProperty<string> Age { get; private set; } public PersonViewModel(Person model) { this.Name = model .ObserveProperty(x => x.Name) // IObservable<string>ã«å¤æã㦠.ToReactiveProperty() // ReactiveProperty<string>ã«å¤æã㦠.SetValidateNotifyError(x => // 空æåã®æã¯ã¨ã©ã¼ã«ãã string.IsNullOrWhiteSpace(x) ? "Name is required" : null); this.Age = model .ObserveProperty(x => x.Age) // IObservable<int>ã«å¤æã㦠.Select(x => x.ToString()) // IObservable<string>ã«å¤æã㦠.ToReactiveProperty() // ReactiveProperty<string>ã«å¤æã㦠.SetValidateNotifyError(x => // intåã«å¤æã§ããªãå ´åã¯ã¨ã©ã¼ã«ãã { int result; // no use return int.TryParse(x, out result) ? null : "Error"; }); } }
ããããã£ã®ã¨ã©ã¼ã®æç¡ã¯ãHasErrorsããããã£ã§ç¢ºèªã§ãã¾ãã
var p = new Person { Name = "tanaka", Age = 10 }; var vm = new PersonViewModel(p); Console.WriteLine(vm.Name.HasErrors); // False vm.Name.Value = ""; // Error! Console.WriteLine(vm.Name.HasErrors); // True Console.WriteLine(vm.Age.HasErrors); // False vm.Age.Value = "xxx"; // Error! Console.WriteLine(vm.Age.HasErrors); // True
ã¨ã©ã¼ã¡ãã»ã¼ã¸ã¯ãWPFã®å ´åã¯Validation.Errorsæ·»ä»ããããã£ã®ErrorContentã§åå¾ã§ããã®ã§XAMLã§å®çµã§ãã¾ããWPF以å¤ã®ãã©ãããã©ã¼ã ã§ã¯ãç¹ã«æ¤è¨¼ã¨ã©ã¼ã®ãµãã¼ããç¡ãã®ã§å¤å°ããã©ãããæé ãè¸ããã¨ã«ã«ãªãã¾ããæé ã¯å¥è¨äºã«è²ãã¾ãã
追è¨ï¼ï¼ããã©ãç¡ãããã«ãã¾ããã
ReactiveProperty v1.2.0をリリースしました - かずきのBlog@hatena
追è¨ããã¾ã§
VMã®ããããã£ã«ã¨ã©ã¼ãç¡ãã¨ããã¤ã¾ãReactivePropertyã®HasErrorsããããã£ãFalseã®æã«ãVM â Mã«å¤ãç§»ãã°ããã¨ãããã¨ã«ãªãã¾ãã
public class 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 use return int.TryParse(x, out result) ? null : ""; }); this.Age // VM -> M .Where(_ => !this.Age.HasErrors) // ã¨ã©ã¼ãç¡ãã¨ã㯠.Select(x => int.Parse(x)) // intåã«å¤æã㦠.Subscribe(x => model.Age = x); // æ¸ãæ»ã } }
ããã§ãããªãã¼ã·ã§ã³ã¨ã©ã¼ã®ãªãã¨ãã ãVM â Mã¸å¤ãæ¸ãæ»ãå¦çã®ããViewModelãåºæ¥ã¾ããã
var p = new Person { Name = "tanaka", Age = 10 }; var vm = new PersonViewModel(p); Console.WriteLine(p.Name); // tanaka vm.Name.Value = "kimura"; Console.WriteLine(p.Name); // kimura vm.Name.Value = ""; // Error! Console.WriteLine(p.Name); // kimura
ã³ã¬ã¯ã·ã§ã³ã®ãããã³ã°
Mã¨VMã®å¤ã®é£æºãRxã§ç°¡åã«åºæ¥ãããã«ãªãã¾ããããã ãé常ã¯ãåä¸ã®ãªãã¸ã§ã¯ãã ãã§ã¯ãªããã³ã¬ã¯ã·ã§ã³ã«å ¥ã£ãModelãVMã®ã³ã¬ã¯ã·ã§ã³ã«å¤æããã¨ãã£ãå¦çãããããã¾ãã
ReactivePropertyã§ã¯ããããåç´åããããã«ReadOnlyReactiveCollectionã¯ã©ã¹ãæä¾ãã¦ãã¾ããããã¯ãObservableCollectionããç°¡åã«ä½ããã¨ãã§ãã¾ãã
var m = new ObservableCollection<Person>(); var vm = m.ToReadOnlyReactiveCollection(x => new PersonViewModel(x)); // M -> VMã®å¤æå¦çãæ¸¡ãã¨å¾ã¯ãããããã¦ããã m.Add(new Person { Name = "tanaka", Age = 30 }); Console.WriteLine(vm[0].Name.Value); // tanaka m[0].Name = "kimura"; Console.WriteLine(vm[0].Name.Value); // kimura m.Add(new Person { Name = "nakata", Age = 40 }); Console.WriteLine(vm[1].Name.Value); // nakata
é¢é£
ReactiveProperty オーバービュー - かずきのBlog@hatena
ReactiveProperty/README-ja.md at master · runceel/ReactiveProperty · GitHub