日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Reactive Extensions入门(5):ReactiveUI MVVM框架

發(fā)布時(shí)間:2025/4/5 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Reactive Extensions入门(5):ReactiveUI MVVM框架 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

??? 從前面幾篇文章可以了解到,Rx作為LINQ的一種擴(kuò)展,極大地簡(jiǎn)化了異步編程。但Rx的用法不僅如此,由于其可高的擴(kuò)展性,在其他很多方面也有所應(yīng)用。

??? 在前面例子中,我們使用代碼和UI界面上的元素打交道,這種方式在傳統(tǒng)的Winfom編程中很常見,但是在基于XAML構(gòu)造的界面這種應(yīng)用程序中,這樣顯得不是非常友好,XAML中聲明式編程可以使得程序更加簡(jiǎn)潔,傳統(tǒng)的方式?jīng)]有利用到XAML中強(qiáng)大的綁定功能。之前,我們大量使用了諸如Observable.FromEvent這樣的操作,然后來使用后臺(tái)代碼來設(shè)置控件的屬性,這都是傳統(tǒng)的編程方式。

??? 當(dāng)然,對(duì)于規(guī)模較小的程序來說,這種方式無可厚非。這種方式的最大的缺點(diǎn)在于,對(duì)于測(cè)試很不友好,要測(cè)試這樣的應(yīng)用程序很困難,我們需要?jiǎng)?chuàng)建UI控件并模擬輸入,這樣效率不高而且不可靠。另一個(gè)缺點(diǎn)是,這種方式使得代碼高度耦合而且脆弱。針對(duì)這些問題,一種稱為Model-View-ViewModel(MVVM)的設(shè)計(jì)模式逐漸發(fā)展起來。

??? 結(jié)合MVVM模式和Rx類庫,發(fā)展出了ReactiveUI這個(gè)MVVM框架。他能夠使得應(yīng)用程序可以管理,并能使用聲明式、函數(shù)式的方式來表達(dá)復(fù)雜的對(duì)象間的交互。換句話說,ReactiveUI能夠幫助我們描述屬性之間是如何聯(lián)系起來的,即使有些屬性與異步方法調(diào)用有關(guān)。

?

1. MVVM模式

??? Model-View-ViewModel模式是充分利用XAML設(shè)計(jì)平臺(tái)上的數(shù)據(jù)綁定功能而產(chǎn)生的一種設(shè)計(jì)模式。在該模式中,Model是用來表現(xiàn)應(yīng)用程序的數(shù)據(jù)以及與界面獨(dú)立的邏輯的核心對(duì)象。View就是UI控件及界面,比如窗體控件或者用戶自定義控件。值得注意的是對(duì)于同一數(shù)據(jù)對(duì)象,可能有多種表現(xiàn)形式,比如對(duì)于同一數(shù)據(jù)源,有的視圖用來顯示統(tǒng)計(jì)或者全局的信息,有的顯示每一項(xiàng)的詳細(xì)信息。

??? 一般我們很熟悉的是MVC模型,所以MVVM模式中的ViewModel是其特別的地方。從名字上看,ViewModel是一種針對(duì)視圖的模型。可能有點(diǎn)不好理解。舉個(gè)例子來說:在用戶注冊(cè)頁面(View)中,一般有輸入用戶名,密碼,重復(fù)輸入密碼這幾個(gè)輸入框。在這個(gè)視圖中,用戶名和密碼顯然是存在于Model中,但是 重復(fù)輸入密碼 這一項(xiàng)并不屬于Model,它顯然不應(yīng)該存在于真實(shí)的數(shù)據(jù)模型中,該項(xiàng)只是用在View中。

??? 在傳統(tǒng)的以XAML為界面的程序中,開發(fā)者一般使用頁面(View)的后臺(tái)的代碼來存儲(chǔ)這個(gè)重復(fù)輸入密碼值,但是這樣同樣存在可測(cè)試性和緊耦合的問題。例如,如果我們要測(cè)試“只有當(dāng)密碼和重復(fù)密碼輸入的值匹配,提交按鈕才可以使用”這樣一個(gè)場(chǎng)景就變得有點(diǎn)困難。現(xiàn)在,我們將這個(gè)字段存在另一個(gè)稱之為ViewModel的對(duì)象中,這個(gè)ViewModel對(duì)象只是一個(gè)普通的類,他并不需要繼承自UI控件,我們可以將該對(duì)象看做是與View的交互邏輯。在我們的例子中,驗(yàn)證兩次密碼是否匹配以及在匹配時(shí)讓提交按鈕可用,這些邏輯都應(yīng)該寫在ViewModel對(duì)象中。對(duì)于每一個(gè)View,都應(yīng)該有一個(gè)對(duì)應(yīng)的ViewModel對(duì)象。

?

1.1 ViewModeld的理念

??? MVVM最強(qiáng)大的一方面在于它的目標(biāo)是將一個(gè)命令(command)或者屬性(property)是什么和如何執(zhí)行分開來。ViewModel是對(duì)屬性和命令的一種思考。在傳統(tǒng)的基于Codebehind的用戶交互框架中,開發(fā)者需要思考控件的事件和屬性。當(dāng)以這種方式編寫代碼時(shí),意味著事件和相應(yīng)的控件緊緊的聯(lián)系在一起。使得測(cè)試變得困難,因?yàn)槲覀冃枰M出控件的動(dòng)作才能測(cè)試控件對(duì)應(yīng)的事件及功能是否正常。

當(dāng)使用MVVM的ViewModels時(shí),最重要的是將這兩部分邏輯分開來。在View中決定了這些控件如何被觸發(fā),同時(shí),控件對(duì)應(yīng)的一些屬性利用XAML的綁定技術(shù)和ViewModel綁定起來。

?

1.2 MVVM框架的作用

??? 現(xiàn)在有很多開源的MVVM框架可以使用了,如MVVMLight、Prism,這些框架框架各有優(yōu)點(diǎn)。但是他們都提供了實(shí)現(xiàn)MVVM模式的最基本要素。首先,這些框架為ViewModel對(duì)象提供了一個(gè)基類,當(dāng)這些對(duì)象的屬性在屬性值發(fā)生改變時(shí)會(huì)得到通知,這個(gè)是通過實(shí)現(xiàn)INotifyPropertyChanged接口來完成的,這個(gè)接口很關(guān)鍵,因?yàn)樗ㄖ猇iew需要更新綁定到界面上的數(shù)據(jù)。MVVM提供了處理命令的一套系統(tǒng),當(dāng)用戶發(fā)出一些命令時(shí)它能夠很好的處理。這是通過實(shí)現(xiàn)ICommand接口來實(shí)現(xiàn)的,這個(gè)接口通常包含在UI控件中。

?

2.ReactiveUI庫

??? ReactiveUI類庫是實(shí)現(xiàn)了MVVM模式的框架,他移除了一些Rx和用戶界面進(jìn)行交互的代碼。ReactiveUI的核心思想是使開發(fā)者能夠?qū)傩宰兏约笆录D(zhuǎn)換為IObservable對(duì)象,然后在需要的時(shí)候使用IObservable對(duì)象將這些對(duì)象轉(zhuǎn)換到屬性中來。他的另一個(gè)核心目標(biāo)是可以在ViewModel中相關(guān)屬性發(fā)生變化時(shí)可以可執(zhí)行相應(yīng)的命令。雖然其他的框架也允許這么做,但是ReactiveUI會(huì)在依賴屬性變更時(shí)自動(dòng)的去更新結(jié)果,而不需要通過拉或者調(diào)用類似UpdateTheUI之類的方法。

?

2.1 核心類

ReactiveObject:它是ViewModel對(duì)象,該對(duì)象實(shí)現(xiàn)了INotifyPropertyChanged接口。除此之外,該對(duì)象也提供了一個(gè)稱之為Changed的IObservable接口,允許其他對(duì)象來注冊(cè),從而使得該對(duì)象屬性變更時(shí)能夠得到通知。使用Rx中強(qiáng)大的操作符,我們還可以追蹤到一些狀態(tài)是如何改變的。

ReactiveValidateObject:該對(duì)象繼承自ReactiveObject對(duì)象,它通過實(shí)現(xiàn)IDataErrorInfo接口,利用DataAnnotations來驗(yàn)證對(duì)象。因此屬性的值可以使用一些限制標(biāo)記,UI界面能夠自動(dòng)的在屬性的值違反這些限制時(shí)顯示出這些錯(cuò)誤。

ObservableAsPropertyHelper<T>:該類可以很容易的將IObservable對(duì)想轉(zhuǎn)換為一個(gè)屬性,該屬性存儲(chǔ)該對(duì)象的最新值,并且在屬性值發(fā)生改變時(shí)能夠觸發(fā)NofityPropertyChanged事件。使用該類,我們能夠從IObservable中派生出一些新的屬性。

ReactiveCommand該類實(shí)現(xiàn)了ICommand和IObservable接口,并且當(dāng)Execute執(zhí)行時(shí)OnNext方法就會(huì)被執(zhí)行。該對(duì)象的CanExecute可以通過IObservable<bool>來定義。

ReactiveAsyncCommand該對(duì)象繼承自ReactiveCommand,并且封裝了一種通用的模式。即“觸發(fā)一步命令,然后將結(jié)果封送到dispather線程中”該對(duì)象也允許設(shè)置最大并行值。當(dāng)達(dá)到最大值時(shí),CanExecute方法返回false。

?

3.使用ReactiveObject實(shí)現(xiàn)ViewModels

和其他MVVM框架一樣,ReactiveUI框架有一個(gè)對(duì)象來作為ViewModel類。該對(duì)象和基于傳統(tǒng)的實(shí)現(xiàn)了ViewModel對(duì)象的MVVM框架如Foundation,Cliburn.Micro類似。但是最大的不同在于,ReactiveUI能夠很容易的通過名為Changed的IObservable接口注冊(cè)事件變化。在任何一個(gè)屬性發(fā)生變化時(shí),都會(huì)觸發(fā)通知,客戶端通常只需要關(guān)注感興趣的一兩個(gè)變化了的屬性。使用ReactiveUI,可以通過WhenAny擴(kuò)展方法很容易的獲取這些屬性值:

var newLoginVm = new NewUserLoginViewModel();newLoginVm.WhenAny(x => x.User, x => x.Value) .Where(x => x.Name == "Bob") .Subscribe(x => MessageBox.Show("Bob is already a user!"));IObservable<bool> passwordIsValid = newLoginVm.WhenAny(x => x.Password, x => x.PasswordConfirm,(pass, passConf) => (pass.Value == passConf.Value));

??? WhenAny語法看起來過有點(diǎn)奇怪。方法中第一個(gè)參數(shù)是通過匿名方法定義的一系列屬性。在上面的例子中,我們關(guān)心的是神馬時(shí)候Password或者PasswordConfirm發(fā)生變化。最后一個(gè)參數(shù)和Zip操作符中的類似,他使用一個(gè)匿名方法來將兩個(gè)結(jié)果結(jié)合起來,然后返回結(jié)果。當(dāng)這兩個(gè)屬性中的任何一個(gè)發(fā)生變化時(shí),方法就會(huì)執(zhí)行,并以IObservable的形式返回執(zhí)行結(jié)果,在上面的例子中就是passwordIsValid這個(gè)對(duì)象。

??? 對(duì)于ReactiveObject,值得注意的是,屬性必須明確的使用特定的語法進(jìn)行定義。因?yàn)楹?jiǎn)單的get,set并沒有實(shí)現(xiàn)INotifyPropertyChanged,從而不會(huì)通知ReactiveObject對(duì)象該屬性發(fā)生了改變。唯一例外的就是,如果一個(gè)屬性在構(gòu)造器中初始化了,在以后的程序中不會(huì)發(fā)生改變。在ReactiveObject中,屬性的命名也需要注意,用作屬性的私有字段必須為屬性名稱前面加上下劃線。下面的例子展示了如何使用ReactiveObject聲明一個(gè)可讀寫的屬性。

public class AppViewModel : ReactiveObject {int _SomeProp;public int SomeProp{get { return _SomeProp; }set { this.RaiseAndSetIfChanged(x => x.SomeProp, value); }} }

傳統(tǒng)的實(shí)現(xiàn)IpropertyChangeNofity接口的實(shí)現(xiàn)方法如下:

public class AppViewModel : INotifyPropertyChanged {int _SomeProp;public int SomeProp{get { return _SomeProp; }set{if (_SomeProp == value)return;_SomeProp = value;RaisePropertyChanged("SomeProp");}}public event PropertyChangedEventHandler PropertyChanged;private void RaisePropertyChanged(string propertyName){PropertyChangedEventHandler handler = this.PropertyChanged;if (handler != null){handler(this, new PropertyChangedEventArgs(propertyName));}} }

??? WhenAny實(shí)現(xiàn)了ReactiveUI的核心功能之一,它使得開發(fā)者能夠很容易將相關(guān)屬性變化用IObservable表示。該功能使得可以直接使用Rx以聲明的方式創(chuàng)建狀態(tài)機(jī)。

??? 除了使用Rx來描述復(fù)雜的異步操作事件之外,Rx和ReactiveUI結(jié)合可以使得對(duì)象在某個(gè)特定的狀態(tài)下可以得到通知,即使這種狀態(tài)涉及到多個(gè)不同的對(duì)象或者屬性。

?

4. ReactiveCommand

ReactiveCommand實(shí)現(xiàn)了ICommand接口,他可以模擬簡(jiǎn)單的ICommand實(shí)現(xiàn)。我們可以將它看做是一種ICommand,可以使用Create靜態(tài)方法創(chuàng)建。

var cmd = ReactiveCommand.Create(x => true, x => Console.WriteLine(x)); cmd.CanExecute(null); //方法輸出true cmd.CanExecute("Hello"); //方法輸出"Hello"

下面構(gòu)造了一個(gè)Command,該Command只在鼠標(biāo)松開時(shí)觸發(fā)。

var mouseIsUp = Observable.Merge(Observable.FromEvent<MouseButtonEventArgs>(window, "MouseDown").Select(_ => false),Observable.FromEvent<MouseButtonEventArgs>(window, "MouseUp").Select(_ => true)).StartWith(true); var cmd = new ReactiveCommand(mouseIsUp); cmd.Subscribe(x => Console.WriteLine(x));

??? 上面的例子演示了如何使用IObservable構(gòu)造Command。通常我們使用WhenAny創(chuàng)建IObservable然后構(gòu)造Command對(duì)象。大多數(shù)情況下,只有當(dāng)特定的屬性被設(shè)置或者取消設(shè)置時(shí)會(huì)觸發(fā)Command。例如在之前的NewUserLoginViewModel中。

IObservable<bool> passwordIsValid = newLoginVm.WhenAny( x => x.Password, x => x.PasswordConfirm, (pass, passConf) => (pass.Value == passConf.Value)); var confirmCommand = new ReactiveCommand(passwordIsValid);

??? View通過按鈕或者菜單綁定confirmCommand,當(dāng)在兩次密碼不匹配時(shí),按鈕或者菜單就會(huì)呈現(xiàn)出灰色。當(dāng)密碼或者重復(fù)密碼輸入框中的值發(fā)生變化時(shí),ReactiveCommand就會(huì)重新求值,來決定是否使得按鈕或者菜單可用。

??? 值得注意的是,當(dāng)屬性發(fā)生變化時(shí),Command的CanExecute會(huì)立即自動(dòng)更新,而不依賴于CommandManager.RequerySuggested。在WPF或者Silverlight中存在這個(gè)bug,除非你切換焦點(diǎn)或者點(diǎn)擊,按鈕不會(huì)重新改變其狀態(tài)。使用IObservable意味著Commanding框架確切的知道在狀態(tài)發(fā)生改變時(shí),不需要重新手動(dòng)執(zhí)行頁面上的每一個(gè)Command對(duì)象。

??? ReactiveCommand對(duì)象本身可以被注冊(cè),并且在執(zhí)行Exectue方法時(shí),提供一些有用的信息。這表明,訂閱者可以執(zhí)行一些Reactive可以執(zhí)行的一些動(dòng)作,使得我們能夠更好的進(jìn)行控制。如下:

var cmd = new ReactiveCommand(); cmd.Where(x => ((Int32)x % 2 == 0)).Subscribe(x => Console.WriteLine("{0} is Even numbers .", x)); cmd.Where(x => ((Int32)x % 2 != 0)).Timestamp().Subscribe(x => Console.WriteLine("{0} is Odd,{1}", x.Value, x.Timestamp));cmd.Execute(2);//輸出“2 is Even numbers. cmd.Execute(3);//輸出 3 is Odd,2012/3/4 20:38:51 +08:00

?

4.1使用ObservableAsPropertyHelper將Observables轉(zhuǎn)化為Properties

??? 使用WhenAny方法,可以監(jiān)視對(duì)象屬性的變化,并針對(duì)這些變化生成IObservable對(duì)象。但是有時(shí)候,我們想將這些生成的IObservable對(duì)象設(shè)置為一種輸出屬性。想象一下有這樣一個(gè)場(chǎng)景,有一個(gè)取色器,用戶能夠通過3個(gè)Slide分別設(shè)置R,G,B值。每一個(gè)Slide可以使用ViewModel對(duì)象來表示,取值范圍為0到1。為了顯示結(jié)果,我們需要將RGB合成為一個(gè)XAML顏色對(duì)象。當(dāng)RGB中的任何一個(gè)發(fā)生變化時(shí),我們需要更新顏色屬性。

??? 我們可以常簡(jiǎn)單的通過WhenAny創(chuàng)建一個(gè)IObservable<Color>對(duì)象,但是我們想將這個(gè)值存回到屬性中。ReactiveUI提供了一個(gè)稱之為ObservableAsPropertyHelper的對(duì)象,該對(duì)象可以存儲(chǔ)IObservable中的最新值。為了演示這一操作,我們需要?jiǎng)?chuàng)建一個(gè)“輸出屬性”

ObservableAsPropertyHelper<Color> finalColor; public Color FinalColor {get {return finalColor.Value;} }

??? 注意到屬性并沒有set方法,這是因?yàn)閷傩允怯蒊Observable生成的,而不需要手動(dòng)設(shè)定。在ViewModel的構(gòu)造函數(shù)中,我們將描述如何從RGB產(chǎn)生FinalColor:

IObservable<Color> color = this.WhenAny(x => x.Red, x => x.Green, x => x.Blue, (r, g, b) => new Color(r.Value, g.Value, b.Value)); finalColor = color.ToProperty(this, x => x.FinalColor);

??? 這一步只需在構(gòu)造函數(shù)中執(zhí)行一次。現(xiàn)在只要Red,Green,或者Blue中的任何一個(gè)發(fā)生變化,FinalColor對(duì)象都會(huì)更新以反映最新的變化值。

??? ReactiveObject和ReactiveCommad是創(chuàng)建ViewModel對(duì)象的兩個(gè)核心工具。使用它們我們可以使用屬性和命令以及通過描述屬性和命令之間的動(dòng)態(tài)關(guān)系來構(gòu)建一個(gè)View。當(dāng)我們關(guān)心狀態(tài)變化,以及某一個(gè)屬性的變化對(duì)另外一個(gè)變化產(chǎn)生的影像時(shí)時(shí),我們可以將屬性轉(zhuǎn)換為IObservable對(duì)象。這一點(diǎn)可以幫助我們很好地測(cè)試ViewModel對(duì)象。

ReactiveUI還有一些功能能夠幫助我們?cè)谟脩艚缑嫔蟽?yōu)雅的處理異步方法調(diào)用。幾乎大部分的應(yīng)用程序都需要運(yùn)行后臺(tái)程序,Reactive的靈活方便的異步操作能力使得ReactiveUI在獲取這些異步計(jì)算結(jié)果時(shí)變得很容易。

?

4.2使用ReactiveAsyncCommand處理異步方法調(diào)用

??? 在Winform或者WPF應(yīng)用程序中,如果事件執(zhí)行需要耗費(fèi)很長時(shí)間,比如讀取一個(gè)很大的文件,那么UI很容易卡死。這是因?yàn)槌绦蛟诿τ谔幚砦募x寫操作或者在等待網(wǎng)絡(luò)數(shù)據(jù)傳輸而不能夠刷新用戶界面。在Silverligh或者Windows Phone中通過規(guī)定UI線程不允許阻塞來解決了這一問題。

??? 通常解決這一問題的辦法是另外開一個(gè)線程或者使用線程池來處理這些耗時(shí)操作,但是這又帶來了第二個(gè)問題,那就是所有基于XAML的框架都是線程關(guān)聯(lián)的(thread affinity),這意味著,我們只能夠從創(chuàng)建該對(duì)象的那個(gè)線程訪問該對(duì)象。所以如果您在非UI線程中更新UI,比如執(zhí)行完了一些操作后直接進(jìn)行類似textbox.text=results這類的更新就會(huì)拋出錯(cuò)誤。因?yàn)榉荱I線程不能夠更新UI上的對(duì)象。

傳統(tǒng)的解決這一方法是在更新UI操作時(shí)調(diào)用Dispatcher.BeginInvoke方法,該方法要求代碼在UI線程中運(yùn)行,大致代碼如下:

void OnSomeUIEvent(object o, EventArgs e) {var someData = this.SomePropertyICanOnlyGetOnTheUIThread;var t = new Task(() => {var result = DoSomethingInTheBackground(someData);Dispatcher.BeginInvoke(new Action(() => {this.UIPropertyThatWantsTheCalculation = result;}));};t.Start(); }

??? ReactiveAsyncCommand將這一模式進(jìn)行了一定的封裝,使得我們編寫代碼更加容易。例如:用戶界面上有時(shí)需要某個(gè)異步方法在某一段時(shí)間運(yùn)行,在異步方法運(yùn)行的過程中讓一些按鈕或者控件處于Disable狀態(tài)。稍微友好一些的用戶界面在后臺(tái)正在進(jìn)行的操作時(shí)給UI界面一些提示,比如在界面上顯示,“程序正在進(jìn)行xxx……”的提示,這樣顯得更加友好。

??? 由于ReactiveAsyncCommand直接繼承自ReactiveCommand,所以它能做基類的所有功能。使用Execute,使得Command開始在后臺(tái)執(zhí)行時(shí)并可以通知用戶。ReactiveAsyncCommand和ReactiveCommand不同之處在于,它內(nèi)建了能夠自動(dòng)跟蹤后臺(tái)線程中運(yùn)行的任務(wù)的數(shù)量。

??? 下面是一個(gè)簡(jiǎn)單的使用Command的例子,它在后臺(tái)線程的Task中運(yùn)行,并且只運(yùn)行一次。

var cmd = new ReactiveAsyncCommand(); cmd.RegisterAsyncAction(i => {Thread.Sleep((int)i * 1000); });cmd.Execute(5); cmd.CanExecute(5);//False

??? ReactiveAsyncCommand對(duì)象中是使用RegisterAsyncAction來注冊(cè)異步執(zhí)行操作的。它能夠注冊(cè)異步方法和同步方法,這些方法將會(huì)在后臺(tái)線程中執(zhí)行,并返回IObservable數(shù)據(jù)表示執(zhí)行結(jié)果會(huì)在未來的某一時(shí)刻到來。IObservale通常對(duì)應(yīng)Command調(diào)用。每一次執(zhí)行Execute方法將會(huì)將結(jié)果存入到IObservable對(duì)象中。

?

4.3構(gòu)造一個(gè)ViewModel例子

??? 講了這麼多ReactiveUI框架的幾個(gè)重要對(duì)象。現(xiàn)在用一個(gè)簡(jiǎn)單的View以及與之相關(guān)聯(lián)的VeiwModel來展示如何使用ViewModel。本例子將展示如何執(zhí)行一些簡(jiǎn)單的和按鈕相關(guān)的命令,并模擬在后臺(tái)執(zhí)行一些費(fèi)時(shí)的操作。然后將結(jié)果顯示在UI界面上。

??? 首先來看看我們的前臺(tái)頁面,也就是View,在這里我建立的是一個(gè)簡(jiǎn)單的WPF應(yīng)用程序。

<Window x:Class="RxUI.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="MainWindow" Height="350" Width="525" x:Name="Window"><Grid DataContext="{Binding ViewModel, ElementName=Window}"><StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"><TextBlock Text="{Binding DataFromTheInternet}" FontSize="18"/><Button Content="Click me!" Command="{Binding GetDataFromTheInternet}"CommandParameter="5" MinWidth="75" Margin="0,6,0,0"/></StackPanel></Grid> </Window>

??? View中有幾個(gè)地方我們需要注意。首先我們將頂級(jí)Grid容器的DataContext參數(shù)綁定到我們的ViewModel對(duì)象上。這樣,當(dāng)我們使用XAML數(shù)據(jù)綁定時(shí),這些元素相對(duì)于ViewModel而不是View來進(jìn)行綁定。然后我們定義了一個(gè)TextBlock,將其內(nèi)容綁定到DataFromTheInternet屬性上。最后,我們綁定Button的Command屬性到我們?cè)賄iewModel中定義的一個(gè)稱之為GetDataFromTheInternet的Command對(duì)象上。相關(guān)的定義以及ViewModel代碼如下:

public partial class MainWindow : Window {public AppViewModel ViewModel { get; protected set; }public MainWindow(){ViewModel = new AppViewModel();InitializeComponent();} }class AppViewModel:ReactiveObject {ObservableAsPropertyHelper<String> dataFromTheInternet;public string DataFromTheInternet{get { return dataFromTheInternet.Value; }}public ReactiveAsyncCommand GetDataFromTheInternet { get; protected set; } }

??? 在View中,我們通過get set方法創(chuàng)建了一個(gè)名為ViewModel的普通屬性,然后我們?cè)贅?gòu)造函數(shù)的InitializeComponet方法之前初始化了該屬性。接著,我們定義了一個(gè)ViewModel類來對(duì)我們的View進(jìn)行建模。通過ObservableAsPropertyHelper以及ReactiveAsyncCommand定義了一個(gè)輸出屬性。必須是屬性才在XAML中綁定,屬性的setter是Protected的,因?yàn)槲覀冎恍枰跇?gòu)造函數(shù)中進(jìn)行實(shí)例化,之后就不會(huì)再對(duì)其進(jìn)行設(shè)置了。

??? 接下來就到了比較關(guān)鍵的部分了-ViewModel的構(gòu)造函數(shù)。ReactiveUI關(guān)注定義和描述屬性和命令之間的相關(guān)關(guān)系,所以最重要的代碼就在ViewModel的構(gòu)造函數(shù)中,可以將這部分工作看作是屬性之間的相互關(guān)聯(lián)。這種方法的好處是,所有交互的代碼都在這里,而不是分散在后臺(tái)代碼的事件處理和回調(diào)方法中。對(duì)于很多ViewModel來說,可能只有在構(gòu)造函數(shù)中有一些代碼。

public AppViewModel() {GetDataFromTheInternet = new ReactiveAsyncCommand();var futureData = GetDataFromTheInternet.RegisterAsyncAction(I => {Thread.Sleep(5 * 1000);return String.Format("The Future will be {0}x as awesome!", i);});dataFromTheInternet = futureData.ToProperty(this, x => x.DataFromTheInternet); }

??? 每一次用戶點(diǎn)擊按鈕的時(shí)候,Command的Execute方法就會(huì)被執(zhí)行一次,每5分鐘就會(huì)向futureData這個(gè)Observable對(duì)象中傳入一個(gè)數(shù)據(jù)。程序運(yùn)行結(jié)果如下:

?

?

???? 上面的代碼很簡(jiǎn)潔,我們沒有任何顯示的定義異步方法比如聲明一個(gè)Task或者開一個(gè)線程,也沒有將返回的結(jié)果進(jìn)行封送然后調(diào)用Dispatcher.BeginInvoke來更新UI界面的代碼。整個(gè)代碼看起來像是一個(gè)簡(jiǎn)單的單線程的應(yīng)用程序。而且進(jìn)一步,這種方式極大的提高了可測(cè)試性。

??? 使用Dispatcher.BeginInvoke意味著我們假定Dispatcher存在并且起作用。但是在一個(gè)單元測(cè)試中,這個(gè)是不存在的。ReactiveUI會(huì)自動(dòng)的刪除這些代碼并將他們換成默認(rèn)的IScheduler而不使用Dispatcher.

???? 使用ReactiveAsyncCommad,代碼可以在后臺(tái)線程中運(yùn)行,前臺(tái)UI依舊能夠響應(yīng)用戶的操作。但是,一些長時(shí)間運(yùn)行的操作,比如Web請(qǐng)求,并不需要頻繁的進(jìn)行重復(fù)。這些數(shù)據(jù)應(yīng)該緩存起來,使得不同的請(qǐng)求只請(qǐng)求一次。

?

5.ReactiveUI中的緩存

??? 緩存在實(shí)際開發(fā)中應(yīng)用的很廣泛。最常用的做法是在本地維護(hù)一個(gè)查找表,以存儲(chǔ)最近獲取的數(shù)據(jù),當(dāng)再次請(qǐng)求這些數(shù)據(jù)時(shí),先查看查找表中是否存在,如果存在就直接讀取,而不用再一次請(qǐng)求。每一種緩存方案都應(yīng)該有緩存機(jī)制,例如規(guī)定緩存何時(shí)過期,如何移除過期的數(shù)據(jù)等等。有時(shí)候不恰當(dāng)?shù)臋C(jī)制,比如只往緩存中添加數(shù)據(jù),而不移除過期的數(shù)據(jù),會(huì)導(dǎo)致內(nèi)存泄露。

??? 在ReactiveUI中,引入了一個(gè)稱之為MemorizingMRUCache的對(duì)象,如名字所示,他是一種以最近最常使用過的數(shù)據(jù)來作為緩存方案,它會(huì)移除一些在一定時(shí)間內(nèi)沒有請(qǐng)求的數(shù)據(jù),從而保證緩存集在一定的大小范圍內(nèi)。

?

5.1使用MemorizingMRUCache

??? 調(diào)用MemorizingMRUCache的Get方法就可以從緩存中獲取對(duì)應(yīng)的值,構(gòu)造緩存時(shí)需要在其構(gòu)造函數(shù)中出傳入緩存函數(shù),該緩存函數(shù)必須是一種數(shù)學(xué)形態(tài)的,也就是說對(duì)于任何一個(gè)相同的給定參數(shù),其返回值時(shí)也應(yīng)該是相同的。另外一個(gè)需要注意的地方是他和QueuedAsyncMRUCache不同,他不是線程安全的。如果在多線程中使用該緩存對(duì)象,則需要加鎖。下面的例子簡(jiǎn)單演示了MemorizingMRUCache的使用方法。

var cache = new MemoizingMRUCache<Int32, Int32>((x, ctx) => {Thread.Sleep(5 * 1000);return x * 100; },20);cache.Get(10);//第一次獲取,需要5秒 cache.Get(10);//第二次取值,立即返回 cache.Get(15);//也需要5秒

?

5.2維護(hù)磁盤緩存

??? MemorizingMRUCache也可以將緩存數(shù)據(jù)從內(nèi)存中存儲(chǔ)到磁盤上供以后使用,緩存的鍵可以是一個(gè)URL,值可以是該URL對(duì)應(yīng)的臨時(shí)文件。當(dāng)緩存文件不再需要時(shí),調(diào)用OnRelease方法可以刪除這些臨時(shí)文件,下面是一些比較有用的函數(shù)。

  • TryGet:視圖從緩存中獲取某一個(gè)鍵對(duì)應(yīng)的值
  • Invalidate:將某一個(gè)鍵對(duì)應(yīng)的值的緩存進(jìn)行清除,內(nèi)部調(diào)用Release函數(shù)。
  • InvalidateAll:清空所有緩存。

?

5.3 異步緩存結(jié)果

??? ObservableMemorizingMRUCache是一種線程安全的MemorizingMRUCache異步版本。如上所述,MemorizingMRUCache可以緩存一些需要大量計(jì)算的結(jié)果,但是它具有的缺點(diǎn)是其本身是單線程的結(jié)構(gòu),如果使用多線程訪問或者試圖緩存同時(shí)多個(gè)web請(qǐng)求的結(jié)果,就會(huì)產(chǎn)生問題。

??? ObservableMemorizingMRUCache解決了這一問題,同時(shí)提供了稱之為AsyncGet的方法,該方法返回一個(gè)IObservable對(duì)象。該對(duì)象在異步命令返回時(shí)返回,而且只執(zhí)行一次。

??? 例如,假設(shè)我們要寫一個(gè)微博客戶端,需要獲取每條信息發(fā)布者的人物圖像,如果用傳統(tǒng)的foreach方法的話,可能會(huì)比較慢。即使采用傳統(tǒng)的異步方式獲取,仍然存在有獲取相同信息發(fā)布者的相同的人物圖像的情況。

??? ObservableMemorizingMRUCache解決了這個(gè)問題。在前面的例子中,我們獲取所有的微博信息集合,然后異步的請(qǐng)求發(fā)布者圖像信息。對(duì)以第一條記錄,我們發(fā)出WebRequest請(qǐng)求的時(shí)候,緩存中為空。然后我們請(qǐng)求第二條數(shù)據(jù),這是時(shí)候,第一條數(shù)據(jù)可能還沒有返回,我們又請(qǐng)求了同一個(gè)圖像。如果某一個(gè)人發(fā)了50條微博信息,那么這樣的請(qǐng)求就會(huì)產(chǎn)生50次。

??? 當(dāng)我們調(diào)用AsyncGet方法時(shí),我們檢查緩存,而且也需要檢查請(qǐng)求列表。對(duì)于每一個(gè)可能的輸入,我們可以認(rèn)為他有三種狀態(tài),要么處于cache中,要么正在請(qǐng)求中,要么是全新的一個(gè)請(qǐng)求。ObservableAsyncMRUCache可以保證這三種狀態(tài)能夠以一種線程安全的方式正確處理。由于AsyncGet是一個(gè)異步方法,它能夠和ReactiveAsyncCommand很好的協(xié)同工作,我們可以將他作為RegisterAsyncObservable方法的一個(gè)參數(shù)。最后的結(jié)果是一個(gè)Command對(duì)象,該對(duì)象從后臺(tái)獲取數(shù)據(jù),然后自動(dòng)的維持最小的請(qǐng)求數(shù)據(jù),減輕并發(fā)量,而且緩存了重復(fù)的請(qǐng)求數(shù)據(jù)。

??? 講了這么多,最后我們將以一個(gè)例子展示ReactiveUI的應(yīng)用。

?

6.使用ReactiveUI開發(fā)一個(gè)異步圖片搜索工具

??? 這是一個(gè)使用Flickr來進(jìn)行照片搜索的例子,當(dāng)然您也可以使用Bing等搜索引擎。當(dāng)用戶停止在輸入框輸入內(nèi)容時(shí),系統(tǒng)使用用戶輸入的關(guān)鍵字進(jìn)行查詢,然后將查詢結(jié)果顯示出來。界面如下:

?

?

6.1 設(shè)計(jì)MVVM

??? 使用ReactiveUI框架的最主要目的是使用MVVM模式來開發(fā)程序,整個(gè)應(yīng)用程序包含兩個(gè)類。MainWindow這個(gè)是View,對(duì)應(yīng)的AppViewModel是ViewModel。

??? 在MainWindow中,我們需要?jiǎng)?chuàng)建一個(gè)AppViewModel簡(jiǎn)單屬性,然后在MainWindows的構(gòu)造函數(shù)的InitializeComponet()方法之前實(shí)例化AppViewModel對(duì)象。

public partial class MainWindow : Window {public AppViewModel ViewModel { get; protected set; }public MainWindow(){ViewModel = new AppViewModel();InitializeComponent();} }

??? 對(duì)于AppViewModel類,使其繼承自ReactiveObject對(duì)象,然后定義一個(gè)SearchTerm屬性和ExecuteSearch命令,如下:

public class AppViewModel:ReactiveObject {String _SearchTerm;public String SearchTerm {get { return _SearchTerm; }set { this.RaiseAndSetIfChanged(x => x.SearchTerm, value); }}public ReactiveAsyncCommand ExecuteSearch { get; protected set; } }

?

6.2將IObservable對(duì)象轉(zhuǎn)換為屬性

?

??? 在ReactiveUI中,我們可以將IObservable轉(zhuǎn)換為屬性,當(dāng)Observable對(duì)象有新的值加入時(shí),就會(huì)通知ReactiveObject對(duì)象更新其屬性值。

??? 前面講到,要實(shí)現(xiàn)這個(gè)轉(zhuǎn)換需要用到ObservableAsPropertyHelper類,這個(gè)類注冊(cè)一個(gè)Observable對(duì)象并存儲(chǔ)其最新值的一份拷貝。一般在ReactiveObject對(duì)象的RaisePropertyChanged方法調(diào)用時(shí)就會(huì)執(zhí)行相應(yīng)的操作。

ObservableAsPropertyHelper<List<FlickrPhoto>> _SearchResults; public List<FlickrPhoto> SearchResults {get { return this._SearchResults.Value; }} ObservableAsPropertyHelper<Visibility> _SpinnerVisibility; public Visibility SpinnerVisibility { get { return _SpinnerVisibility.Value; } }

???? 上面創(chuàng)建一個(gè)屬性,用來控制Spinner控件的顯示,在應(yīng)用程序忙時(shí)給出提示。然后,我們創(chuàng)建一個(gè)構(gòu)造函數(shù),定義兩個(gè)可選屬性,來方便測(cè)試。

public AppViewModel(ReactiveAsyncCommand testExecuteSearchCommand = null, IObservable<List<FlickrPhoto>> testSearchResults = null){ExecuteSearch = testExecuteSearchCommand ?? new ReactiveAsyncCommand();……}

??? ViewModel中的屬性是彼此相互聯(lián)系的,傳統(tǒng)的方法很難簡(jiǎn)潔的描述他們之間的關(guān)系,如“當(dāng)程序正在搜索時(shí),顯示Spinner”,這個(gè)簡(jiǎn)單的關(guān)系通常會(huì)涉及到好幾個(gè)事件處理。使用ReactiveUI能夠以一種很整潔清晰的方式定義各個(gè)屬性之間的關(guān)系。

我們需要將屬性轉(zhuǎn)換為Observable對(duì)象,當(dāng)搜索的關(guān)鍵字發(fā)生變化時(shí),Observable就會(huì)返回一個(gè)對(duì)象。和之前的例子一樣,我們使用Throttle操作符來忽略一些不必要的頻繁的操作。我們并不想監(jiān)聽鍵盤每一次按下事件,我們監(jiān)聽變化的值,忽略兩次相同的查詢以及為空的查詢。

??? 最后,使用RxUI的InvoleCommand方法,該方法接受String類型,然后調(diào)用ExecuteSearch的Execute方法。

this.ObservableForProperty(x => x.SearchTerm).Throttle(TimeSpan.FromMilliseconds(800), RxApp.DeferredScheduler).Select(x => x.Value).DistinctUntilChanged().Where(x => !String.IsNullOrWhiteSpace(x)).InvokeCommand(ExecuteSearch);

??? 當(dāng)正在運(yùn)行查詢時(shí),我們需要顯示Spinner控件,ReactiveUI能夠描述這種狀態(tài)。ExecuteSearch有一個(gè)稱之為ItemsInFlight的IObservable<int>屬性,當(dāng)有新的值產(chǎn)生或者移除時(shí),會(huì)觸發(fā)該屬性發(fā)生變化,我們可以將這些信息和Visibility屬性結(jié)合起來,當(dāng)該值等于0時(shí)隱藏,大于0時(shí)顯示。然后使用ToProperty操作符來創(chuàng)建ObservableAsPropertyHelper對(duì)象。

spinnerVisibility = ExecuteSearch.ItemsInflight.Select(x => x > 0 ? Visibility.Visible : Visibility.Collapsed).ToProperty(this, x => x.SpinnerVisibility, Visibility.Hidden);

??? 然后,我們需要定義當(dāng)命令觸發(fā)時(shí)應(yīng)該執(zhí)行的操作。在命令執(zhí)行時(shí),我們需要調(diào)用GetSearchResultsFromFlicker方法。值得注意的是,該方法的返回結(jié)果是一個(gè)Observable集合,每一次執(zhí)行操作時(shí),都會(huì)返回一個(gè)List類型的FlickerPhoto的Observable對(duì)象。

下面是構(gòu)造函數(shù)中的方法和GetSearchResultsFromFlicker函數(shù)。

IObservable<List<FlickrPhoto>> results;if (testSearchResults != null){results = testSearchResults;}else{results = ExecuteSearch.RegisterAsyncFunction(term => GetSearchResultsFromFlickr((String)term));}_SearchResults = results.ToProperty(this, x => x.SearchResults, new List<FlickrPhoto>());private static List<FlickrPhoto> GetSearchResultsFromFlickr(string searchTerm) {var doc = XDocument.Load(String.Format(CultureInfo.InvariantCulture,"http://api.flickr.com/services/feeds/photos_public.gne?tags={0}&format=rss_200",HttpUtility.UrlEncode(searchTerm)));if (doc.Root == null)return null;var titles = doc.Root.Descendants("{http://search.yahoo.com/mrss/}title").Select(x => x.Value);var tagRegex = new Regex("<[^>]+>", RegexOptions.IgnoreCase);var descriptions =doc.Root.Descendants("{http://search.yahoo.com/mrss/}description").Select(x => tagRegex.Replace(HttpUtility.HtmlDecode(x.Value), ""));var items = titles.Zip(descriptions,(t, d) => new FlickrPhoto { Title = t, Description = d }).ToArray();var urls = doc.Root.Descendants("{http://search.yahoo.com/mrss/}thumbnail").Select(x => x.Attributes("url").First().Value);var ret = items.Zip(urls, (item, url) =>{item.Url = url; return item;}).ToList();return ret; }

??? 程序的后臺(tái)代碼寫好了,前臺(tái)代碼如下,圖中紅色方框部分就是綁定ViewModel數(shù)據(jù)部分。可以看到UI界面上的控件都以聲明的方式基本都和ViewModel部分的數(shù)據(jù)綁定好了,使得View的后臺(tái)頁面基本上沒有什么代碼,您是否體會(huì)到了XAML的強(qiáng)大的數(shù)據(jù)綁定能力呢。

?

?

?? 編譯運(yùn)行,下面是程序運(yùn)行結(jié)果。

?

?

7.總結(jié)

??? 本文介紹了ReactiveUI這個(gè)和Rx結(jié)合緊密的MVVM框架,它使得我們開發(fā)的基于XAML的程序更加直觀,簡(jiǎn)潔和可維護(hù)。另外使用Rx和ReactiveUI,使得程序的能夠很方便的進(jìn)行測(cè)試,可以使用Rx和ReactiveUI來模擬整個(gè)流程,當(dāng)然這也是所有MVVM框架要達(dá)到的目的。本文代碼點(diǎn)擊此處下載,希望本文對(duì)您了解ReactiveUI及MVVM有所幫助。

總結(jié)

以上是生活随笔為你收集整理的Reactive Extensions入门(5):ReactiveUI MVVM框架的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。