MVVM模式構(gòu)建WPF_第1頁
MVVM模式構(gòu)建WPF_第2頁
MVVM模式構(gòu)建WPF_第3頁
MVVM模式構(gòu)建WPF_第4頁
MVVM模式構(gòu)建WPF_第5頁
已閱讀5頁,還剩15頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

1、使用MVVM設(shè)計模式構(gòu)建WPF應(yīng)用程序本文是翻譯大牛 Josh Smith 的文章,WPF Apps With The Model-View-ViewModel Design Pattern , 譯者水平有限,如有什么問題請看原文,或者與譯者討論(非常樂意與你討論)。本文討論的內(nèi)容:WPF與設(shè)計模式MVP模式對 WPF來說為什么 MVVM是更好的選擇用MVVM 構(gòu)建 WPF程序本文涉及的技術(shù):WPF、數(shù)據(jù)綁定內(nèi)容列表有序與混亂模型-視圖-視圖模型的演變?yōu)槭裁碬PF開發(fā)者喜歡MVVM 演示程序中繼命令邏輯ViewModel類層級結(jié)構(gòu)ViewModelBase 類CommandViewModel

2、類Mai nWin dowViewModel 類View 對應(yīng) ViewModel數(shù)據(jù)模型和Repository新增客戶數(shù)據(jù)表單所有客戶視圖總結(jié)開發(fā)UI,對一個專業(yè)軟件并不容易。 它需要未知數(shù)據(jù)、 交互式設(shè)計,可視化設(shè)計、聯(lián)通性, 多線程、國際化、驗證、單元測試以及其他的一些東西才能完成??紤]到UI要展示開發(fā)的系統(tǒng)并且必須滿足用戶對系統(tǒng)風(fēng)格不可預(yù)知的變更,因此它是很多應(yīng)用程序最脆弱的地方。有很多的設(shè)計模式可以幫助解決UI不斷變更這頭難纏的野獸,但是恰當(dāng)?shù)姆蛛x和描述多個關(guān)注點可能很困難。模式越復(fù)雜,之后用到的捷徑越可能破壞之前正確的努力。這并不總是設(shè)計模式的錯。有時使用要寫很多的代碼復(fù)雜設(shè)計模式

3、,這是因為我們使用的UI平臺并不適合簡單是設(shè)計模式。UI平臺需要做的是很容易使用簡單的,久經(jīng)考驗的,開發(fā)者認(rèn)識的設(shè)計模式構(gòu)建UI。慶幸的是,WPF就是這樣一個平臺。隨著是使用 WPF開發(fā)的比例不斷升高,WPF社區(qū)發(fā)展了自己的模式與實踐生態(tài)圈子。在本文,我將討論一些設(shè)計與實現(xiàn)客戶端應(yīng)用程序的WPF最佳實踐。利用 WPF和MVVM設(shè)計模式銜接的一些核心功能,我將通過一個例子介紹,用“正確”的方式構(gòu)建一個WPF程序是多么的簡單。data templates, comma nds, data binding, the resource system 以及 MVVM 模式怎么揉合至 U 起創(chuàng)建一個簡單的

4、、可測試的、健壯的框架,并且任何WPF程序都能使用,到文章最后,這一切都很清晰明了。文中的例程可以作為現(xiàn)實中一個WPF應(yīng)用程序的模版,并且使用MVVM設(shè)計模式作為其核心架構(gòu)。例程解決方案中的單元測試部分,展示了測試ViewModel 類的功能是很容易的。在深入本文之前,我們首先看一下我們要使用像 MVVM 這樣的設(shè)計 模式。有序與混亂沒有必要在一個” Hello,World! ”的程序中使用設(shè)計模式。任何一個合格的開發(fā)者看一眼就 指導(dǎo)那幾行代碼是干什么的。 然而隨著程序功能點的增加, 隨之代碼的數(shù)量以及移動部件也 會增多。 最終系統(tǒng)的復(fù)雜度以及不斷出現(xiàn)問題, 促使開發(fā)者組織他們的代碼, 以便它

5、們更容 易理解, 討論、 擴(kuò)展以及維護(hù)。 我們通過給代碼中某些實體命以眾所周知的名字, 減少復(fù)雜 系統(tǒng)認(rèn)知誤區(qū)。我們給函數(shù)塊命名主要依據(jù)系統(tǒng)中的功能角色。開發(fā)者有意識的根據(jù)設(shè)計模式組織他們的代碼, 而不是根據(jù)設(shè)計模式自動去組織。 無論哪一 種,都沒有什么問題。但是在本文中,我說明在WPF 程序中明確使用 MVVM 模式的好處。某些類的名稱,包括 MVVM 模式中著名的術(shù)語,如果類是 view 的抽象類就以 ViewModel 結(jié)束。這種方式有助于避免之前提到的認(rèn)知誤區(qū)。相反,你也可以讓那種受控的誤區(qū)存在, 這正是大部分軟件開發(fā)項目的自熱狀態(tài)。模型 -視圖-視圖模型的演變 自從人們開始構(gòu)建 UI

6、 時,就有很多流行的設(shè)計模式讓 UI 構(gòu)建更容易。 比如, MVP 模式在各 種 UI 編程平臺中都非常流行。 MVP 是 MVC 模式的一種變體, MVC 模式已經(jīng)流行了幾十年 了。以防你之前從沒用過 MVP 模式,這里做一個簡單的解釋。你在屏幕上看到的是 View , 它顯示的數(shù)據(jù)是 Model ,Presenter 就是把兩者聯(lián)系起來。 View 依賴 Presenter 并通過 Presenter 展示 Model 數(shù)據(jù),響應(yīng)用戶輸入,提供數(shù)據(jù)驗證(或許委托給 Model 去完成)以及其他的 一些任務(wù)。如果你想了解更過關(guān)于 MVP 模式,我建議你去讀 Jean-Paul Boodhoo

7、 的 August 2006 Design Patterns column 。2004 年晚些時候, Martin Fowler 發(fā)表了一篇叫 Presentation Model ( PM)的模式。PM 模式 和 MVP 類似, MVP 是把一個 View 從行為和狀態(tài)分離出來。 PM 中令人關(guān)注的部分是創(chuàng)建 view 的抽象,叫做 Presentation Model 。之后, View 就僅僅是 Presentation Model 的展示了。在 Fowler的論文中,他展示了 Presentation Model經(jīng)常更新 View,以便兩個彼此同步。同步邏 輯組作為代碼存在于 Pres

8、entation Model 類中。2005年,John Gossman,目前是微軟WPF和 Silverlight架構(gòu)師,在他的博客上披露了Model-View-ViewModel (MVVM) 模式。 MVVM 和 Fowler 的 Presentation Model 是一致的,兩 個模式的特征都是 View的抽象,都包含了 View的行為和狀態(tài)。Fowler引入Presentation Model 是作為創(chuàng)建獨立平臺的 View 的抽象,而 Gossman 引入 MVVM 是作為標(biāo)準(zhǔn)化的方法,利用 WPF的核心特點去簡化 UI的創(chuàng)建。從這種意義上來講,我把MVVM作為一般PM模式的一個

9、特例。在 Glenn Block 一遍優(yōu)秀的文章"Prism: Patterns for Building Composite Applications with WPF", 于2008年9月微軟大會發(fā)布,他解釋了 WPF微軟組合程序開發(fā)向?qū)?。術(shù)語ViewModel沒有用到,然而 PM 卻用來描述 View 的抽象。這篇文章自始至終,都沒沒有出現(xiàn)我要將 MVVM 模式,以及View的抽象ViewModel。我發(fā)現(xiàn)這個術(shù)語在 WPF和Silverlight社區(qū)中比較流行。 不像 MVP 中的 Presenter, ViewModel 不需要引用 View。 View 綁定 V

10、iewModel 的屬性, ViewMode 向 Viewl 暴露 Model 對象的數(shù)據(jù)以及其他的狀態(tài)。 View 和 ViewModel 之間的綁定 很容易構(gòu)造,因為 ViewModel 對象可以設(shè)置為 View 的 DataContext 。如果 ViewModel 中的 屬性值發(fā)生改變,新值將通過綁定自動傳送給View。當(dāng)用戶點擊View中的按鈕時,ViewMode對于的Comma nd將執(zhí)行請求的動作。ViewModel,絕不是 View,去執(zhí)行實體對象的修改。View類并不知道 Model類是否存在,同時 ViewModel和Model也不知道 View。實際上, Model 完全

11、不知道 ViewModel 和 View 存在,這是一個非常松耦合的設(shè)計,在很多方面都有 好處,這不就你就會看到。為什么 WPF 開發(fā)者喜歡 MVVM一旦開發(fā)者適應(yīng)了 WPF和MVVM,就很難區(qū)別兩者。因為MVVM非常適合 WPF平臺,并且WPF被設(shè)計使用 MVVM模式更容易構(gòu)建應(yīng)用程序, MVVM就成了 WPF開發(fā)者的通用語。事實上,微軟內(nèi)部正在用 MVVM開發(fā) WPF應(yīng)用程序,像Microsoft Expression Blend,然而當(dāng) 時WPF平臺的核心功能依然在開發(fā)之中。WPF的很多方面,像控制模型以及數(shù)據(jù)模版,都利用了 MVVM 推薦的顯示狀態(tài)和行為分離技術(shù)。MVVM之所以成為一個

12、偉大設(shè)計模式,是因為WPF的一個最重要的特征數(shù)據(jù)綁定構(gòu)造。通過把 Viewde 屬性綁定到 ViewModel ,你就可以得到兩者松耦合的設(shè)計,并且完全去除 ViewModel 更新 View 的那部分代碼。數(shù)據(jù)綁定系統(tǒng)支持輸入驗證,并且輸入驗證提供了傳 遞錯誤給 View 的標(biāo)準(zhǔn)方法。另兩個 WPF的特點,數(shù)據(jù)模版和資源系統(tǒng)讓MVVM模式更加可用。數(shù)據(jù)模版把View應(yīng)用在 ViewModel 對象上,以便其能夠在 UI 上顯示。你可以在 Xaml 中聲明模版,讓資源系統(tǒng) 在系統(tǒng)運行過程中自動定位并應(yīng)用這些模版。你可以從我 2008 年 7月寫的一篇文章 , "Data and WP

13、F: Customize Data Display with Data Binding and WPF." ,獲取更多關(guān)于綁定和數(shù)據(jù)模版 的信息。要不是 WPF 對 Command 的支持, MVVM 模式就不會那么強(qiáng)大。本文中,我會為你展示ViewModel怎樣把 Comma nds暴露給 View,并且讓 View消費它的功能。如果你對 Comma nd 不是很熟悉,我推薦你讀一下 2008 年 9 月 Brian Noyes 發(fā)布的文章, "Advanced WPF: Understanding Routed Events and Commands in WPF&qu

14、ot; 。除了 WPF( Silverlight2 )本身讓MVVM以一種自然的方式去構(gòu)建程序之外,造成MVVM模式流行還有一個原因,那就是 ViewModel 類很容易進(jìn)行單元測試。從某種意義來講, View 和單元測試只是 ViewModel 兩個不同類型的消費者。擁有一套應(yīng)用程序的單元測試,可以 為提供更自由、快速的回歸測試,而回歸測試有助于降低之后應(yīng)用的維護(hù)成本。 除了促進(jìn)創(chuàng)建自動化回歸測試外, ViewModel 類的可測試性也有助于設(shè)計更容易分離的 UI。 當(dāng)你設(shè)計應(yīng)用時, 你可以通過想象某些東西是否要創(chuàng)建單元測試消費 ViewModel ,來確定它 們是放到 View 里面還是

15、ViewModel 里面。 如果你可以為 ViewModel 寫單元測試而不用創(chuàng)建 任何 UI 控件,你也可以把 ViewModel 剝離出來,因為它不依賴任何具體可視化的組件。 最后,對于要和設(shè)計者合作的開發(fā)者來說,使用 MVVM 模式使得創(chuàng)建平滑的開發(fā) /設(shè)計工作 流更加容易。既然 View 可以是 ViewModel 的任意一個消費者,就很容易去掉一個 View 通 過新增一個 View 去渲染 ViewModel 。這個簡單的步驟允許設(shè)計師構(gòu)建快速原型以及評估 UI 設(shè)計。這樣開發(fā)團(tuán)隊可以關(guān)注創(chuàng)建健壯的 ViewModel 類,而設(shè)計團(tuán)隊可以關(guān)注設(shè)計界面友好的 View。 要融合兩個團(tuán)

16、隊輸出只需要在 View 的 xaml 上進(jìn)行正確的綁定即可。演示程序到此為止,我們回顧了MVVM的歷史以及具體操作理論。我也說明了它在WPF開發(fā)者中間如此流行的原因?,F(xiàn)在是時候繼續(xù)我們的步伐,看一下MVVM 模式在實際中的應(yīng)用。這篇文章中的演示程序以各種方式使用 MVVM 設(shè)計模式,它提供了豐富的例子,幫助在上下文 中理解 MVVM 的概念。我用 VS2008 SP1 創(chuàng)建的這個演示程序,框架是 Microsoft .NET Framework 3.5 SP1。單元測試是用的 Visual Studio unit testing。應(yīng)用可以包含任意數(shù)量的“Workspace”,每一個都可以由用

17、戶點擊左側(cè)導(dǎo)航區(qū)的命令鏈接打開。所有的 Workspace 寄宿在主區(qū)域 TabControl 中,用戶可以通過點擊 workspace 的 tab item 上關(guān)閉按鈕關(guān)閉 workspace。應(yīng)用程序有兩個可用的workspace : "All Customers"和"NewCustomer"。運行程序,打開一些workspace, UI看起來如圖1所示。勺 MWMSfflwApU ® 口jMl lustrumAll Cwstwnrri 遇J£us(nr«rNjnn*T嵌訓(xùn)* !rrunj 敏 on)o網(wǎng)一 Qi(8.8

18、33.163An©canTx>so .ram(12.81,73Hdrun CornetalexQ) contaw. com3*7 3,7M.02Knlich E»s«nlllipQCOntMOUQWII1 PeopleGreggregi 低 ontwsommIrmCTaFtQf>9CvntQ>SQbCOrTiHinkson. GrJinthmkson cxxntow 工 omMcCorta txnuedem seco<it »o .com拍.址氧ASjonlji "0 亡 Xi c4o .c omTotdt 昶leciM

19、 tares; i2.e?X2&圖 1 Workspaces一次只有一個“ All Customers" Workspace的實例可以打開,但是可以打開多個New CustomerWorkspace。當(dāng)用戶決定創(chuàng)建一個新的客戶時,她必須填完圖2所示的數(shù)據(jù)輸入表單。All 亡ustornerE | New Custon>er ,x | Kew Custcm-er J£j|Customer type: (Not Spetfi&d)CustOfD&r fype must set&ctMIFirst rarne;F*ret name is mi

20、ssingILast narprLast name rs m<55?ryIE-rnan:E rnffFi missingI圖2新客戶數(shù)據(jù)輸入表單填完數(shù)據(jù)輸入表單的所有有效值點擊“ Save”按鈕,新客戶的名稱將會出現(xiàn)在tab item上面,同時新客戶也會增加到客戶列表中。應(yīng)用程序不支持刪除或者編輯客戶,但是這和其它功能類似,很容易在已有的程序架構(gòu)上去實現(xiàn)?,F(xiàn)在你已經(jīng)對演示程序有了更深層次的理解了, 接下來我們研究它是如何設(shè)計以及實現(xiàn)的。中繼命令邏輯(Relayi ng Comma nd Logic)除了類構(gòu)造器里調(diào)用初始化組件標(biāo)準(zhǔn)的樣板代碼,應(yīng)用中的每一 View的codebehind文

21、件都是空的。實際上你可以移除View的codebehind文件,程序讓人能夠爭正確的編譯和運行。盡管View中沒有事件處理方法,但是當(dāng)用戶點擊按鈕時,程序依然能夠響應(yīng)并滿足用戶的請求。之所以這樣,是因為UI上Hyperli nk、 Button以及Me nultem 控件的Comma nd屬性被綁定了。綁定機(jī)制確保當(dāng)用戶在控件上點擊時,由ViewModel暴露的ICommand對象能夠執(zhí)行。你可以把 comma nd對象看作一個適配器,這個適配器讓comma nd對象很容易消費在View中聲明的 ViewModel功能。當(dāng) ViewModel 暴露 ICommad 類型的實例屬性,被暴露的 C

22、ommand 對象使用 ViewModel 中 的對象去完成它的工作。 其中一個可能的實現(xiàn)模式是在 ViewModel 內(nèi)創(chuàng)建一個私有嵌套類, 以便 command 能夠訪問包含在 ViewModel 中的私有成員,而不至于污染命名空間。嵌套類 實現(xiàn)了 ICommand 接口,包含在 ViewModel 中對象的引用注入到其構(gòu)造器中。但是為 ViewModel 暴露的每個 Command 創(chuàng)建實現(xiàn) ICommad 的嵌套類,會增加 ViewModel 類的大 小。更多的代碼意味著存在 BUGS 潛力更大。在演示程序中, RelayCommand 類解決了這個問題。 RelayCommand 允

23、許通過把委托傳給其 構(gòu)造器,以實現(xiàn)對命令邏輯的注入。這種方式允許在 ViewMode 類中可以簡單明了的實現(xiàn) Command。RelayCommand 是 DelegateCommand 的一個簡單的變體, DelegateCommand 可以在 Microsoft Composite Application Library 找到。 RelayCommand 類代碼如圖 3 所示。圖 3 RelayCommand 類public class RelayCommand : ICommand#region Fieldsreadonly Action<object> _execute;r

24、eadonly Predicate<object> _canExecute;#endregion / Fields#region Constructorspublic RelayCommand(Action<object> execute): this(execute, null)public RelayCommand(Action<object> execute, Predicate<object> canExecute)if (execute = null)throw new ArgumentNullException("execu

25、te");_execute = execute;_canExecute = canExecute;#endregion / Constructors#region ICommand MembersDebuggerStepThroughpublic bool CanExecute(object parameter)return _canExecute = null ? true : _canExecute(parameter);public event EventHandler CanExecuteChangedadd CommandManager.RequerySuggested +

26、= value; remove CommandManager.RequerySuggested -= value; public void Execute(object parameter)_execute(parameter);#endregion / ICommand Members作為接口 ICommad 實現(xiàn)一部分,事件 CanExecuteChanged 有一些值得關(guān)注的特征。它委托 訂閱 CommandManager. RequerySuggested 事件。這樣以確保無論何時調(diào)用內(nèi)置命令時, WPF 命令架構(gòu)都能調(diào)用所有能夠執(zhí)行的 RelayCommand 對象。RelayCom

27、mand _saveCommand;public ICommand SaveCommandgetif (_saveCommand = null)_saveCommand = new RelayCommand(param => this.Save(),param => this.CanSave );return _saveCommand;ViewModel 類層級圖大部分 ViewModel 類有共同的特征,他們要實現(xiàn) INotifyPropertyChanged 接口,需要顯示一 個友好的名字,以之前說道 Workspace 為例,它需要能夠關(guān)閉(即從 UI 上移除)。要解決這 個

28、問題,自然就需要創(chuàng)建一個或二個 ViewModel 基類,以便新的 ViewModel 類能夠從基類 集成通用的功能。所有的 ViewModel 類形成如圖 4 的層級圖。CwrtiimVirModcl.3:<尿口ft9 i »沱入AUC ut>o«n> uV Jim M odf IDass< WorkipsL-e- / 甘呂 hBeflMaanW indiowV Bew M ocfe-lClass專 Wo rhjspac # i ebd#IOi>pGMb4er i PraptfrtittJi幽r 軍lidPrpcr聊* Method!.:*

29、OnDrapcSeQnP斗 Vsr時PrpfKrfyNHRtk Eveb/ PrcpenyClhtng«lLCDmmdrbdTVirw Model Hittiftractcms * YwWtoiJrBflK-Picpeues* F紹商亍 Conrrind M«h仙Comrrk&ndView McsdelL_J"Properties.呵少 OnReQu«tOciie 丹0>rkip尿凹Events華 RetpuntOose圖4繼承層級圖 為你的ViewModel創(chuàng)建一個基類并不是必須。如果你喜歡在類中通過組合幾個小一點的類 以獲得那些功能,而

30、不是用繼承的方式,這并沒有什么問題。就像任何其他的設(shè)計模式一樣, MVVM是一套指導(dǎo)方針,而不是規(guī)則。ViewModelBase 類ViewModelBase是層級中的根類,這就是它要實現(xiàn)通用INotifyPropertyChanged接口以及有一個 DisplayName 屬性的原因。INotifyPropertyChanged 接口包含一個叫 PropertyChanged 的 事件。無論何時 ViewModel對象的屬性的發(fā)生改變時,它都會觸發(fā)PropertyChanged事件,把新值通知給 WPF綁定系統(tǒng)。根據(jù)通知,綁定系統(tǒng)檢索屬性,UI組件上綁定的屬性將接受新值。為了讓W(xué)PF知道是那

31、一個屬性發(fā)生了改變,PropertyChangedEventArgs類暴露了一個 string類型的屬性 PropertyName。你一定要為事件參數(shù)傳遞正確的屬性名,否則WPF將會為新值檢索出一個錯誤的屬性。ViewModelBase 一個值得關(guān)注的地方就是它為給定的屬性名提供了驗證,驗證屬性是否存在ViewModel對象上。重構(gòu)時,這非常有用。因為通過VS 2008重構(gòu)功能去改變屬性名,不會更新源代碼中字符串,而這些字符串正好包含屬性名(其實不應(yīng)該包含)。在事件參數(shù)中傳 很難追蹤, 因此這個細(xì)微的特征將會節(jié)省大量的時間。 ViewModelBase 中增加了這個有用的 特征,其代碼如下:圖

32、 5 屬性驗證/ In ViewModelBase.cspublic event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged(string propertyName)this.VerifyPropertyName(propertyName);PropertyChangedEventHandler handler = this.PropertyChanged;if (handler != null)var e = new PropertyChangedEventArgs(

33、propertyName);handler(this, e);Conditional("DEBUG")DebuggerStepThroughpublic void VerifyPropertyName(string propertyName)/ Verify that the property name matches a real,/ public, instance property on this object.if (TypeDescriptor.GetProperties(this)propertyName = null)string msg = "In

34、valid property name: " + propertyName;if (this.ThrowOnInvalidPropertyName)throw new Exception(msg);elseDebug.Fail(msg);CommandViewModel 類CommandViewModel 是最簡單的 ViewModelBase 子類,它暴露了一個類型為 ICommad 的 Command 屬性。 MainWindowViewModel 通過 Commands 屬性暴露了 CommandViewModel 對象的一個集合。主窗口左手側(cè)的導(dǎo)航區(qū)域,顯示了MainWind

35、owViewModel 暴露每個CommandViewModel 對象鏈接,像"View all customers ”和"Create new customer ”。當(dāng)用戶 點擊鏈接,將會執(zhí)行相應(yīng)的 Command ,在主窗口的 TabControl 中打開一個 workspace 。 CommandViewModel 類的定義如下所示:public class CommandViewModel : ViewModelBasepublic CommandViewModel(string displayName, ICommand command)if (command =

36、 null)throw new ArgumentNullException("command");base.DisplayName = displayName;this.Command = command;public ICommand Command get; private set; 在 MainWindowResources.xaml 文件中存在一個 key 為 CommandsTemplate 的數(shù)據(jù)模版, 主窗 口( MainWindow )使用這個模版渲染之前提到的 CommandViewModel 對象集合。這個模版 是簡單在 ItemsControl 里把每

37、個 CommandViewModel 對象渲染成一個鏈接,每個鏈接的 Command 屬性綁定到 CommandViewModel 對象的 Command 屬性。數(shù)據(jù)模版 Xaml 如圖 6 所示:圖6渲染Comma nd列表<!- In MainWindowResources.xaml -><!-This template explains how to render the list of commands onthe left side in the main window (the 'Control Panel' area).-><Data

38、Template x:Key="CommandsTemplate"><ItemsControl ItemsSource="Binding Path=Commands"><ItemsControl.ItemTemplate><DataTemplate><TextBlock Margin="2,6"><Hyperlink Command="Binding Path=Command"><TextBlock Text="Binding Pat

39、h=DisplayName" /></Hyperlink></TextBlock></DataTemplate></ItemsControl.ItemTemplate></ItemsControl></DataTemplate>MainWindowViewModel 類 如前面看到的類圖一樣, WorkspaceViewModel 類繼承于 ViewModelBase 并增加了“關(guān)閉” 的能力。這個“關(guān)閉” ,我的意思是在運行的時候能把 workspace 從 UI 上移除。有三個類繼 承于 Workspa

40、ceViewModel ,他們分別為 MainWindowViewModel , AllCustomersViewModel 和 CustomerViewModel 。 MainWindowViewModel 的關(guān)閉請求是由 App 類處理的,其中 App 類創(chuàng)建了 MainWindow 以及它對應(yīng)的 ViewModel 對象。創(chuàng)建代碼如圖7 所示 .圖 7 創(chuàng)建 ViewModel/ In App.xaml.csprotected override void OnStartup(StartupEventArgs e)base.OnStartup(e);MainWindow window =

41、new MainWindow();/ Create the ViewModel to which/ the main window binds.string path = "Data/customers.xml"var viewModel = new MainWindowViewModel(path);/ When the ViewModel asks to be closed,/ close the window.viewModel.RequestClose += delegatewindow.Close();/ Allow all controls in the win

42、dow to/ bind to the ViewModel by setting the/ DataContext, which propagates down/ the element tree.window.DataContext = viewModel;window.Show();MainWindow 包含一個菜單項,該菜單項的 Command 屬性綁定到 MainWindowViewModel 上的 CloseCommand 屬性上。當(dāng)用戶點擊該菜單, App 類響應(yīng)請求,調(diào)用窗體的關(guān)閉方法。 菜單 Xaml 如下所示:<!- In MainWindow.xaml ->&l

43、t;Menu><MenuItem Header="_File"><MenuItem Header="_Exit" Command="Binding Path=CloseCommand" /></MenuItem><MenuItem Header="_Edit" /><MenuItem Header="_Options" /><MenuItem Header="_Help" /></Menu&g

44、t;MainWindowViewModel 包含了 WorkspaceViewModel 對象一個 observable 類型的集合, 該集 合的名稱為 Workspaces。主窗體包含了一個 TabControl,其ItemsSource綁定到上述的集合。 每一個 tab item 都有一個關(guān)閉按鈕,其 Command 屬性綁定到它對應(yīng) WorkspaceViewModel 實例的 CloseCommand 上。模版展示了如何渲染一個帶關(guān)閉按鈕的tab item 。配置 tab item模版的簡化版會展示在下面代碼中,這段代碼可以在 MainWindowResources.xaml 文件中找

45、 到。<DataTemplate x:Key="ClosableTabItemTemplate"><DockPanel Width="120"><ButtonCommand="Binding Path=CloseCommand"Content="X"DockPanel.Dock="Right"Width="16" Height="16"/><ContentPresenter Content="Bindin

46、g Path=DisplayName" /></DockPanel></DataTemplate>當(dāng)用戶點擊tab item上的關(guān)閉按鈕時,會執(zhí)行 WorkspaceViewModel的CloseCommand,觸 發(fā)它的 Requestclose事件。MainWindowViewModel 會監(jiān)控 workspace 的 Requestclose事件, 根據(jù)請求從 Workspaces 集合中移除相應(yīng)的workspace 。因為 MainWindow 的 TabControl 的ItemsSource 綁定到 WorkspaceViewModel 的

47、observable 集合, 從集合中移除對象, 會引起從 TabControl 中移除相應(yīng)的 workspace。 MainWindowViewModel 相應(yīng)的邏輯如圖 8 所示 圖 8 從 UI 上移除 workspace/ In MainWindowViewModel.csObservableCollection<WorkspaceViewModel> _workspaces;public ObservableCollection<WorkspaceViewModel> Workspacesgetif (_workspaces = null)_workspace

48、s = new ObservableCollection<WorkspaceViewModel>(); _workspaces.CollectionChanged += this.OnWorkspacesChanged;return _workspaces;void OnWorkspacesChanged(object sender, NotifyCollectionChangedEventArgs e)if (e.NewItems != null && e.NewItems.Count != 0)foreach (WorkspaceViewModel worksp

49、ace in e.NewItems)workspace.RequestClose += this.OnWorkspaceRequestClose;if (e.OldItems != null && e.OldItems.Count != 0)foreach (WorkspaceViewModel workspace in e.OldItems)workspace.RequestClose -= this.OnWorkspaceRequestClose;void OnWorkspaceRequestClose(object sender, EventArgs e)this.Wor

50、kspaces.Remove(sender as WorkspaceViewModel);在 UnitTests 項目中, MainWindowViewModelTests.cs 文件包含了一個測試方法,該方法驗證 上述功能是否正確執(zhí)行。 很容易為 ViewModel 類創(chuàng)建單元測試是 MVVM 模式的一個大賣點, 因為它只需測試應(yīng)用程序的功能,而不用寫和 UI 交互的代碼。上述測試方法圖 9 所示 圖 9 測試方法/ In MainWindowViewModelTests.csTestMethodpublic void TestCloseAllCustomersWorkspace()/ Cr

51、eate the MainWindowViewModel, but not the MainWindow.MainWindowViewModel target =new MainWindowViewModel(Constants.CUSTOMER_DATA_FILE);Assert.AreEqual(0, target.Workspaces.Count, "Workspaces isn't empty.");/ Find the command that opens the "All Customers" workspace.CommandVie

52、wModel commandVM = target.Commands.First(cvm => cvm.DisplayName = "View all customers");/ Open the "All Customers" workspace. commandVM.Command.Execute(null);Assert.AreEqual(1, target.Workspaces.Count, "Did not create viewmodel.");/ Ensure the correct type of workspa

53、ce was created.var allCustomersVM = target.Workspaces0 as AllCustomersViewModel; Assert.IsNotNull(allCustomersVM, "Wrong viewmodel type created.");/ Tell the "All Customers" workspace to close. allCustomersVM.CloseCommand.Execute(null); Assert.AreEqual(0, target.Workspaces.Count,

54、 "Did not close viewmodel."); 把 View 應(yīng)用到 ViewModel 上MainWindowViewModel 間接從主窗體的 TabControl 控件中增加移除 WorkspaceViewModel 對象。通過數(shù)據(jù)綁定 , TabItem 的 Content 屬性顯示繼承 于 ViewModelBase 的對象。ViewModelBase并不是一個 UI元件,因此他并不支持渲染它自己。 默認(rèn)在TextBlock中,WPF 的一個非可視化對象通過調(diào)用 ToString 方法以顯示該對象。 很明顯這不是你想要的, 除非你 的用戶迫切的想知道

55、ViewModel 的類型名。我們通過強(qiáng)類型數(shù)據(jù)模版很容易告訴 WPF如何渲染ViewModel對象。強(qiáng)類型數(shù)據(jù)模版 key 屬性名沒有賦值,但是其DataType屬性要賦以類型類的實例。如果WPF要去渲染ViewModel 對象,它會檢查在資源系統(tǒng)范圍內(nèi)是否有一個強(qiáng)類型數(shù)據(jù)模版的DataType 和 ViewModel 對象(或者其基類) 的類型一樣。 如果找到一個這樣的模版的話, 他會用該模版去渲染被 TabItem Content 屬性綁定的 ViewModel 對象。MainWindowResources.xaml 文件中有一個 ResourceDictionary (資源字典) ,該

56、字典被增加到 主窗體的資源層級中,這意味著文件包含的資源在正窗體范圍內(nèi)有效。當(dāng)一個 TabItem 的 Content屬性設(shè)置ViewModel對象時,該字典中的強(qiáng)類型數(shù)據(jù)模版會提供一個View (即用戶自定義控件)去渲染 Tabitem Content。具體如圖10所示圖 10 提供 View<!-This resource dictionary is used by the MainWindow.-><ResourceDictionary xmlns=" xmlns:x="xmlns:vm="clr-namespace:DemoApp.Vie

57、wModel"xmlns:vw="clr-namespace:DemoApp.View"><!-This template applies an AllCustomersView to an instanceof the AllCustomersViewModel class shown in the main window.-><DataTemplate DataType="x:Type vm:AllCustomersViewModel"><vw:AllCustomersView /></Dat

58、aTemplate><!-This template applies a CustomerView to an instanceof the CustomerViewModel class shown in the main window.-><DataTemplate DataType="x:Type vm:CustomerViewModel"><vw:CustomerView /></DataTemplate><!- Other resources omitted for clarity. -></ResourceDictionary>你不需要寫任何代碼去決定哪一個View去展示ViewModel對象。WPF資源系統(tǒng)把你從繁重的工作解脫出來,讓你去關(guān)注更重要的事情。在復(fù)雜的場景中,可能需要通過編程去選擇View,但是在大部分情況下,通過編程選擇View是不必要的。The Data Model and Repository 數(shù)據(jù)模型和存儲庫 你已經(jīng)知道應(yīng)用程序如何去加載, 顯示以及關(guān)閉一個 ViewModel 對象?,F(xiàn)在一切已經(jīng)就位, 你可以在整個應(yīng)用程序范圍內(nèi),回顧一下具體實現(xiàn)的細(xì)節(jié)。在深入理解應(yīng)用程序的兩個

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論