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ù)免費(fèi)閱讀

下載本文檔

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

文檔簡(jiǎn)介

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

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

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

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

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

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

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

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

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

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

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

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

13、F: Customize Data Display with Data Binding and WPF." ,獲取更多關(guān)于綁定和數(shù)據(jù)模版 的信息。要不是 WPF 對(duì) Command 的支持, MVVM 模式就不會(huì)那么強(qiáng)大。本文中,我會(huì)為你展示ViewModel怎樣把 Comma nds暴露給 View,并且讓 View消費(fèi)它的功能。如果你對(duì) 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模式流行還有一個(gè)原因,那就是 ViewModel 類很容易進(jìn)行單元測(cè)試。從某種意義來講, View 和單元測(cè)試只是 ViewModel 兩個(gè)不同類型的消費(fèi)者。擁有一套應(yīng)用程序的單元測(cè)試,可以 為提供更自由、快速的回歸測(cè)試,而回歸測(cè)試有助于降低之后應(yīng)用的維護(hù)成本。 除了促進(jìn)創(chuàng)建自動(dòng)化回歸測(cè)試外, ViewModel 類的可測(cè)試性也有助于設(shè)計(jì)更容易分離的 UI。 當(dāng)你設(shè)計(jì)應(yīng)用時(shí), 你可以通過想象某些東西是否要?jiǎng)?chuàng)建單元測(cè)試消費(fèi) ViewModel ,來確定它 們是放到 View 里面還是

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

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

17、戶點(diǎn)擊左側(cè)導(dǎo)航區(qū)的命令鏈接打開。所有的 Workspace 寄宿在主區(qū)域 TabControl 中,用戶可以通過點(diǎn)擊 workspace 的 tab item 上關(guān)閉按鈕關(guān)閉 workspace。應(yīng)用程序有兩個(gè)可用的workspace : "All Customers"和"NewCustomer"。運(yùn)行程序,打開一些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一次只有一個(gè)“ All Customers" Workspace的實(shí)例可以打開,但是可以打開多個(gè)New CustomerWorkspace。當(dāng)用戶決定創(chuàng)建一個(gè)新的客戶時(shí),她必須填完圖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ù)輸入表單的所有有效值點(diǎn)擊“ Save”按鈕,新客戶的名稱將會(huì)出現(xiàn)在tab item上面,同時(shí)新客戶也會(huì)增加到客戶列表中。應(yīng)用程序不支持刪除或者編輯客戶,但是這和其它功能類似,很容易在已有的程序架構(gòu)上去實(shí)現(xiàn)?,F(xiàn)在你已經(jīng)對(duì)演示程序有了更深層次的理解了, 接下來我們研究它是如何設(shè)計(jì)以及實(shí)現(xiàn)的。中繼命令邏輯(Relayi ng Comma nd Logic)除了類構(gòu)造器里調(diào)用初始化組件標(biāo)準(zhǔn)的樣板代碼,應(yīng)用中的每一 View的codebehind文

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

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

23、許通過把委托傳給其 構(gòu)造器,以實(shí)現(xiàn)對(duì)命令邏輯的注入。這種方式允許在 ViewMode 類中可以簡(jiǎn)單明了的實(shí)現(xiàn) Command。RelayCommand 是 DelegateCommand 的一個(gè)簡(jiǎn)單的變體, 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 實(shí)現(xiàn)一部分,事件 CanExecuteChanged 有一些值得關(guān)注的特征。它委托 訂閱 CommandManager. RequerySuggested 事件。這樣以確保無論何時(shí)調(diào)用內(nèi)置命令時(shí), WPF 命令架構(gòu)都能調(diào)用所有能夠執(zhí)行的 RelayCommand 對(duì)象。RelayCom

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

28、問題,自然就需要?jiǎng)?chuàng)建一個(gè)或二個(gè) ViewModel 基類,以便新的 ViewModel 類能夠從基類 集成通用的功能。所有的 ViewModel 類形成如圖 4 的層級(jí)圖。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時(shí)PrpfKrfyNHRtk Eveb/ PrcpenyClhtng«lLCDmmdrbdTVirw Model Hittiftractcms * YwWtoiJrBflK-Picpeues* F紹商亍 Conrrind M«h仙Comrrk&ndView McsdelL_J"Properties.呵少 OnReQu«tOciie 丹0>rkip尿凹Events華 RetpuntOose圖4繼承層級(jí)圖 為你的ViewModel創(chuàng)建一個(gè)基類并不是必須。如果你喜歡在類中通過組合幾個(gè)小一點(diǎn)的類 以獲得那些功能,而

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

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

32、 5 屬性驗(yàn)證/ 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 是最簡(jiǎn)單的 ViewModelBase 子類,它暴露了一個(gè)類型為 ICommad 的 Command 屬性。 MainWindowViewModel 通過 Commands 屬性暴露了 CommandViewModel 對(duì)象的一個(gè)集合。主窗口左手側(cè)的導(dǎo)航區(qū)域,顯示了MainWind

35、owViewModel 暴露每個(gè)CommandViewModel 對(duì)象鏈接,像"View all customers ”和"Create new customer ”。當(dāng)用戶 點(diǎn)擊鏈接,將會(huì)執(zhí)行相應(yīng)的 Command ,在主窗口的 TabControl 中打開一個(gè) 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 文件中存在一個(gè) key 為 CommandsTemplate 的數(shù)據(jù)模版, 主窗 口( MainWindow )使用這個(gè)模版渲染之前提到的 CommandViewModel 對(duì)象集合。這個(gè)模版 是簡(jiǎn)單在 ItemsControl 里把每

37、個(gè) CommandViewModel 對(duì)象渲染成一個(gè)鏈接,每個(gè)鏈接的 Command 屬性綁定到 CommandViewModel 對(duì)象的 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)閉” 的能力。這個(gè)“關(guān)閉” ,我的意思是在運(yùn)行的時(shí)候能把 workspace 從 UI 上移除。有三個(gè)類繼 承于 Workspa

40、ceViewModel ,他們分別為 MainWindowViewModel , AllCustomersViewModel 和 CustomerViewModel 。 MainWindowViewModel 的關(guān)閉請(qǐng)求是由 App 類處理的,其中 App 類創(chuàng)建了 MainWindow 以及它對(duì)應(yīng)的 ViewModel 對(duì)象。創(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 包含一個(gè)菜單項(xiàng),該菜單項(xiàng)的 Command 屬性綁定到 MainWindowViewModel 上的 CloseCommand 屬性上。當(dāng)用戶點(diǎn)擊該菜單, App 類響應(yīng)請(qǐ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 對(duì)象一個(gè) observable 類型的集合, 該集 合的名稱為 Workspaces。主窗體包含了一個(gè) TabControl,其ItemsSource綁定到上述的集合。 每一個(gè) tab item 都有一個(gè)關(guān)閉按鈕,其 Command 屬性綁定到它對(duì)應(yīng) WorkspaceViewModel 實(shí)例的 CloseCommand 上。模版展示了如何渲染一個(gè)帶關(guān)閉按鈕的tab item 。配置 tab item模版的簡(jiǎn)化版會(huì)展示在下面代碼中,這段代碼可以在 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)用戶點(diǎn)擊tab item上的關(guān)閉按鈕時(shí),會(huì)執(zhí)行 WorkspaceViewModel的CloseCommand,觸 發(fā)它的 Requestclose事件。MainWindowViewModel 會(huì)監(jiān)控 workspace 的 Requestclose事件, 根據(jù)請(qǐng)求從 Workspaces 集合中移除相應(yīng)的workspace 。因?yàn)?MainWindow 的 TabControl 的ItemsSource 綁定到 WorkspaceViewModel 的

47、observable 集合, 從集合中移除對(duì)象, 會(huì)引起從 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 項(xiàng)目中, MainWindowViewModelTests.cs 文件包含了一個(gè)測(cè)試方法,該方法驗(yàn)證 上述功能是否正確執(zhí)行。 很容易為 ViewModel 類創(chuàng)建單元測(cè)試是 MVVM 模式的一個(gè)大賣點(diǎn), 因?yàn)樗恍铚y(cè)試應(yīng)用程序的功能,而不用寫和 UI 交互的代碼。上述測(cè)試方法圖 9 所示 圖 9 測(cè)試方法/ 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 對(duì)象。通過數(shù)據(jù)綁定 , TabItem 的 Content 屬性顯示繼承 于 ViewModelBase 的對(duì)象。ViewModelBase并不是一個(gè) UI元件,因此他并不支持渲染它自己。 默認(rèn)在TextBlock中,WPF 的一個(gè)非可視化對(duì)象通過調(diào)用 ToString 方法以顯示該對(duì)象。 很明顯這不是你想要的, 除非你 的用戶迫切的想知道

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

56、字典被增加到 主窗體的資源層級(jí)中,這意味著文件包含的資源在正窗體范圍內(nèi)有效。當(dāng)一個(gè) TabItem 的 Content屬性設(shè)置ViewModel對(duì)象時(shí),該字典中的強(qiáng)類型數(shù)據(jù)模版會(huì)提供一個(gè)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>你不需要寫任何代碼去決定哪一個(gè)View去展示ViewModel對(duì)象。WPF資源系統(tǒng)把你從繁重的工作解脫出來,讓你去關(guān)注更重要的事情。在復(fù)雜的場(chǎng)景中,可能需要通過編程去選擇View,但是在大部分情況下,通過編程選擇View是不必要的。The Data Model and Repository 數(shù)據(jù)模型和存儲(chǔ)庫 你已經(jīng)知道應(yīng)用程序如何去加載, 顯示以及關(guān)閉一個(gè) ViewModel 對(duì)象。現(xiàn)在一切已經(jīng)就位, 你可以在整個(gè)應(yīng)用程序范圍內(nèi),回顧一下具體實(shí)現(xiàn)的細(xì)節(jié)。在深入理解應(yīng)用程序的兩個(gè)

溫馨提示

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

最新文檔

評(píng)論

0/150

提交評(píng)論