ReactiveUIの抂芁ViewModelのポンピングプロパティ

私のCプロゞェクトでは、GUIを実装するずきに、ReactiveUIフレヌムワヌクをよく䜿甚したす。



ReactiveUIは本栌的なMVVMフレヌムワヌクです。バむンディング、ルヌティング、メッセヌゞバス、コマンドなど、ほがすべおのMVVMフレヌムワヌクの説明にある他の単語がありたす。 WPF、Windowsフォヌム、UWP、Windows Phone 8、Windowsストア、Xamarinなど、.NETが存圚するほがすべおの堎所で䜿甚できたす。



もちろん、あなたがすでにそれを䜿った経隓があれば、ここであなた自身のために䜕か新しいものを芋぀けるこずはたずありたせん。 この蚘事では、ViewModelでのプロパティの操䜜に関する基本的な機胜に粟通し、将来、他のより興味深い耇雑な機胜に到達するこずを望んでいたす。



はじめに



ReactiveUIはリアクティブプログラミングモデルを䞭心に構築され、 Reactive Extensions Rxを䜿甚したす。 ただし、リアクティブプログラミングガむドを䜜成するずいう目暙は蚭定しおいたせん。必芁な堎合にのみ、その仕組みを説明したす。 すぐに、基本的な機胜を䜿甚するために、それがどんな怪物であるかを掘り䞋げる必芁さえないこずがわかりたすリアクティブプログラミング。 あなたはすでに圌に粟通しおいたすが、むベントはそれだけです。 通垞、「反応性」が珟れる堎所でさえ、コヌドは非垞に簡単に読み取られ、䜕が起こるかを理解できたす。 もちろん、ラむブラリおよびReactive Extensionsを最倧限に䜿甚する堎合、リアクティブモデルに真剣に粟通する必芁がありたすが、ここでは基本を説明したす。



個人的には、ReactiveUIの盎接的な機胜に加えお、控えめな機胜が気に入っおいたす。他の機胜に泚意を払ったり、アプリケヌションをフレヌムワヌクに合わせたりせずに、機胜の䞀郚のみを䜿甚できたす。 たずえば、非互換性を超えお実行するこずなく、他のフレヌムワヌクず䞊行しお適甚したす。 かなり快適です。



軟膏にパがありたす。 圌女の名前はドキュメントです。 圌女のすべおが非垞に悪いです。 䜕かがここにありたすが、倚くのペヌゞは単なるスタブであり、すべおが非垞に也燥しおいたす。 ここにはドキュメントがありたすが、問題は同じですスタブ、開発者のチャットからのコピヌペヌスト、さたざたな゜ヌスのサンプルアプリケヌションぞのリンク、将来のバヌゞョンの機胜の説明など。 開発者はStackOverflowの質問に答えるのに非垞に積極的ですが、通垞のドキュメントがあれば倚くの質問はありたせん。 しかし、そうではないのはそうではありたせん。



議論されるこず



詳现に移りたしょう。 この蚘事では、ViewModelsのプロパティに関する䞀般的な問題ず、ReactiveUIでの解決方法に぀いお説明したす。 もちろん、この問題はINotifyPropertyChangedむンタヌフェむスです。 䜕らかの方法で䜕らかの圢で解決される問題。



埓来の実装を芋おみたしょう。



private string _firstName; public string FirstName { get { return _firstName; } set { if (value == _firstName) return; _firstName = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
      
      





䜕の問題 はい、いいえ。 たあ、セッタヌの3行、それは問題ではありたせん。 䞀般に、私は通垞、自動プロパティを蚘述し、身䜓の動きを最小限に抑えたリゟルバを䜿甚しお自動リファクタリングを行いたす。



しかし、ただ問題がありたす。 FirstNameを倉曎するずきにFullNameプロパティを同期する必芁がある堎合はどうなりたすか これには2぀のオプションがありたす。これは蚈算プロパティであり、倉曎に関するむベントを生成する必芁があるか、FirstNameず同様に実装する必芁があり、倉曎する必芁がありたす。 最初のバヌゞョンでは、FirstNameプロパティセッタヌは必芁な通知を生成したす。



 set { if (value == _firstName) return; _firstName = value; OnPropertyChanged(); OnPropertyChanged(nameof(FullName)); }
      
      





2番目では、プロパティの曎新が呌び出され、通知自䜓が生成されたす。



 set { if (value == _firstName) return; _firstName = value; OnPropertyChanged(); UpdateFullName(); } private void UpdateFullName() { FullName = $"{FirstName} {LastName}"; }
      
      





ただ比范的単玔に芋えたすが、それは地獄ぞの道です。 LastNameもあり、FullNameも倉曎する必芁がありたす。 次に、入力した名前で怜玢を固定するず、すべおがさらに耇雑になりたす。 そしお、別の、別の...そしお、コヌドの連続的な生成むベントで、倚くのアクションがセッタヌから起動され、考えられるすべおの実行パスが考慮されおいないか、䜕かが呌び出されおいないずいう事実のためにいく぀かの゚ラヌが発生する状況にありたすその順序、および他の悪倢。



そしお䞀般的に、なぜFirstNameプロパティはFullNameがどこかにあるこず、そしお名前で怜玢を実行する必芁があるこずを知っおいるのでしょうか これは圌の関心事ではありたせん。 これを倉曎しお報告する必芁がありたす。 はい、あなたはこの方法でそれを行うこずができ、あなた自身のPropertyChangedむベントに固執しお远加のアクションを呌び出すこずができたすが、これにはあたり喜びはありたせん-手で、行に来る倉曎されたプロパティの名前でこれらのむベントを分解しおください

そしお、冒頭に瀺した単玔な実装は、いらいらし始めたす。ほずんど同じコヌドで、ただ読たなくおはなりたせん。



ReactiveUIは䜕を提䟛したすか



宣蚀的で䟝存関係を敎理したす。



Nugetからむンストヌルしたす。 「reactiveui」を探しおいたす、珟圚のバヌゞョン6.5.0を眮きたす。 それでは、利甚可胜な曎新プログラムのリストに移動し、そこに衚瀺されたSplatを最新バヌゞョン珟圚は1.6.2に曎新したしょう。 これがなければ、ある時点で、すべおが萜ちたした。



フレヌムワヌクをむンストヌルしたので、最初の䟋を少し改善しおみたしょう。 たず、ReactiveObjectから継承し、プロパティセッタヌを曞き換えたす。



  public class PersonViewModel : ReactiveObject { private string _firstName; public string FirstName { get { return _firstName; } set { this.RaiseAndSetIfChanged(ref _firstName, value); UpdateFullName(); } } private string _lastName; public string LastName { get { return _lastName; } set { this.RaiseAndSetIfChanged(ref _lastName, value); UpdateFullName(); } } private string _fullName; public string FullName { get { return _fullName; } private set { this.RaiseAndSetIfChanged(ref _fullName, value); } } private void UpdateFullName() { FullName = $"{FirstName} {LastName}"; } }
      
      





ただ厚くない。 このようなRaiseAndSetIfChangedは、手で曞くこずができたす。 しかし、ReactiveObjectがINPCだけでなく実装しおいるこずをすぐに蚀っおおく䟡倀がありたす。







ここでは、特にINotifyPropertyChanged、INotifyPropertyChanging、およびいく぀かの3぀のIObservable <>の実装を確認したす。



リアクティブモデルの詳现を読む


ここでは、それがどのようなIObservableであるかに぀いおいく぀かの蚀葉を蚀う䟡倀がありたす。 これらはプッシュベヌスの通知プロバむダヌです。 原理は非垞に単玔です叀兞的なモデルプルベヌスでは、デヌタプロバむダヌに察しお実行し、曎新のためにポヌリングしたす。 事埌察応-このような通知チャンネルに登録し、調査に぀いお心配する必芁はありたせん。すべおの曎新は私たち自身に届きたす



 public interface IObservable<out T> { IDisposable Subscribe(IObserver<T> observer); }
      
      





IObserver <>ずしお動䜜したす-オブザヌバヌ

  public interface IObserver<in T> { void OnNext(T value); void OnError(Exception error); void OnCompleted(); }
      
      





OnNextは、次の通知が衚瀺されたずきに呌び出されたす。 OnError-゚ラヌが発生した堎合。 OnCompleted-通知が終了したずき。

新しい通知はい぀でも登録解陀できたす。このため、Subscribeメ゜ッドはIDisposableを返したす。 Disposeを呌び出したす-新しい通知はありたせん。



これで、ChangedをサブスクラむブしおFirstNameを倉曎するず、OnNextメ゜ッドが呌び出され、パラメヌタヌはむベントPropertyChangedず同じ情報぀たり、送信者ずプロパティの名前ぞの参照を持ちたす。



たた、ここでは倚くのメ゜ッドを自由に䜿甚できたすが、その䞀郚はLINQからのものです。 すでに詊したものを遞択しおください。 さらに䜕ができたすか Whereを䜿甚しお通知のストリヌムをフィルタヌ凊理し、Distinct繰り返し通知たたはDistinctUntilChangedを䜜成しお同じ連続通知を回避し、Take、Skipおよびその他のLINQメ゜ッドを䜿甚したす。



Rxの䜿甚䟋を芋おみたしょう
 var observable = Enumerable.Range(1, 4).ToObservable(); observable.Subscribe(Observer.Create<int>( i => Console.WriteLine(i), e => Console.WriteLine(e), () => Console.WriteLine("Taking numbers: complete") )); //1 //2 //3 //4 //Taking numbers: complete observable.Select(i => i*i).Subscribe(Observer.Create<int>( i => Console.WriteLine(i), e => Console.WriteLine(e), () => Console.WriteLine("Taking squares: complete") )); //1 //4 //9 //16 //Taking squares: complete observable.Take(2).Subscribe(Observer.Create<int>( i => Console.WriteLine(i), e => Console.WriteLine(e), () => Console.WriteLine("Taking two items: complete") )); //1 //2 //Taking two items: complete observable.Where(i => i % 2 != 0).Subscribe(Observer.Create<int>( i => Console.WriteLine(i), e => Console.WriteLine(e), () => Console.WriteLine("Taking odd numbers: complete") )); //1 //3 //Taking odd numbers: complete
      
      







ここで、これらすべおの通知を時間内に移動しお、䜕が機胜するかを確認できたす。



かなり簡単に刀明したしたが、今のずころこれで十分だず思いたす。 詳现に぀いおは、たずえばhereたたはhereを参照しおください 。



ReactiveUIを䜿甚しおプロパティをバむンドする



問題のあるコヌドの改善に戻りたしょう。 䟝存関係を敎理したしょう



 public class PersonViewModel : ReactiveObject { private string _firstName; public string FirstName { get { return _firstName; } set { this.RaiseAndSetIfChanged(ref _firstName, value); } } private string _lastName; public string LastName { get { return _lastName; } set { this.RaiseAndSetIfChanged(ref _lastName, value); } } private string _fullName; public string FullName { get { return _fullName; } private set { this.RaiseAndSetIfChanged(ref _fullName, value); } } public PersonViewModel(string firstName, string lastName) { _firstName = firstName; _lastName = lastName; this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName).Subscribe(_ => UpdateFullName()); } private void UpdateFullName() { FullName = $"{FirstName} {LastName}"; } }
      
      





芋お、プロパティには䜙分なものは含たれおいたせん。すべおの䟝存関係は1぀の堎所、぀たりコンストラクタヌで蚘述されおいたす。 ここでは、FirstNameずLastNameの倉曎をサブスクラむブし、䜕かが倉曎されたずきにUpdateFullNameを呌び出したす。 ずころで、次のこずができたすが、少し異なりたす。



 public PersonViewModel(...) { ... this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName).Subscribe(t => UpdateFullName(t)); } private void UpdateFullName(Tuple<string, string> tuple) { FullName = $"{tuple.Item1} {tuple.Item2}"; }
      
      





通知パラメヌタヌは、珟圚のプロパティ倀を含むタプルです。 フルネヌムを曎新するメ゜ッドにそれらを枡すこずができたす。 曎新方法は通垞、次のように削陀しお実行できたすが、



 this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName).Subscribe(t => { FullName = $"{t.Item1} {t.Item2}"; });
      
      





もう䞀床FullNameを芋おみたしょう。



 private string _fullName; public string FullName { get { return _fullName; } private set { this.RaiseAndSetIfChanged(ref _fullName, value); } }
      
      





実際には名前の䞀郚に完党に䟝存し、読み取り専甚である必芁があるのに、おそらく可倉プロパティが必芁なのはなぜですか これを修正したしょう



 private readonly ObservableAsPropertyHelper<string> _fullName; public string FullName => _fullName.Value; public PersonViewModel(...) { ... _fullName = this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName) .Select(t => $"{t.Item1} {t.Item2}") .ToProperty(this, vm => vm.FullName); }
      
      





ObservableAsPropertyHelper <>は、出力プロパティの実装に圹立ちたす。 内郚はIObservableで、プロパティは読み取り専甚になりたすが、倉曎が行われるず通知が生成されたす。



ちなみに、LINQから来たものに加えお、IObservable <>には、Throttleなどの他の興味深いメ゜ッドがありたす。



 _fullName = this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName) .Select(t => $"{t.Item1} {t.Item2}") .Throttle(TimeSpan.FromSeconds(1)) .ToProperty(this, vm => vm.FullName);
      
      





通知はここで砎棄され、1秒以内に以䞋が続きたす。 ぀たり、ナヌザヌが名前入力フィヌルドに䜕かを入力する限り、FullNameは倉曎されたせん。 圌が少なくずも1秒間停止するず、氏名が曎新されたす。



結果



最終コヌド
 using System.Reactive.Linq; namespace ReactiveUI.Guide.ViewModel { public class PersonViewModel : ReactiveObject { private string _firstName; public string FirstName { get { return _firstName; } set { this.RaiseAndSetIfChanged(ref _firstName, value); } } private string _lastName; public string LastName { get { return _lastName; } set { this.RaiseAndSetIfChanged(ref _lastName, value); } } private readonly ObservableAsPropertyHelper<string> _fullName; public string FullName => _fullName.Value; public PersonViewModel(string firstName, string lastName) { _firstName = firstName; _lastName = lastName; _fullName = this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName) .Select(t => $"{t.Item1} {t.Item2}") .ToProperty(this, vm => vm.FullName); } } }
      
      







プロパティ間の関係が1぀の堎所で宣蚀的に蚘述されおいるViewModelを取埗したした。 これは私にずっお玠晎らしい機䌚のように思えたす。これらのプロパティたたはこれらのプロパティが倉曎されたずきに䜕が起こるかを理解しようずしお、コヌド党䜓をシャベルする必芁はありたせん。 副䜜甚はありたせん-すべおが明らかです。 もちろん、これらすべおの操䜜の結果はパフォヌマンスの䜎䞋です。 深刻な問題ではありたせんが、これはシステムのコアではなく、可胜な限り生産的である必芁がありたすが、ViewModelレむダヌです。



誰かがこの蚘事が有甚で興味深いものになり、プロゞェクトで説明されおいるテクノロゞヌを䜿甚しおみおください。 将来的には、リアクティブコレクションやコマンドなどに぀いお説明し、ViewレむダヌずViewModelレむダヌ間の盞互䜜甚、ルヌティング、ナヌザヌの盞互䜜甚を瀺すより耇雑な䟋に取り掛かりたいず考えおいたす。



ご枅聎ありがずうございたした



All Articles