1.MVVM設計模式簡介
MVVM的設計模式最早於2005年由微軟的WPF和Silverlight架構師John Gossman在他的博客中提到。以下是這篇文章的鏈接:
http://blogs.msdn.com/b/johngossman/archive/2005/10/08/478683.aspx
MVVM設計模式基於MVC這種將UI和邏輯分離的結構思想。傳統的.NET平臺下軟件開發如ASP.NET和WPF/Silverlight大多數是基於CodeBehind這樣的方式,我們往往將所有的代碼全部寫在後臺代碼文件中,例如UI操作,業務邏輯操作,IO,數據服務的調用等等。這雖然表面上有利於“開發效率”,實際上項目結構不清晰,各個模塊之間緊密耦合,不利於擴展,不利於測試。
MV-X的思想,爲.NET平臺下的架構提供一種很好的實踐。使我們可以構建更利於擴展,結構清晰,職責分明,易測試的軟件項目。
但是目前MVVM模式還沒有一個標準的實踐,微軟也還沒有給出相對標準的方案。目前社區討論的主要是MVVM的思想。在實際開發過程中形成了幾種不同的風格。其中以Josh Smith的文章影響比較大:
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
本篇提到的也主要是參考John Smith的思想。
2.採用MVVM設計模式的好處
在Silverlight或者WPF中採用MVVM的架構可以獲得以下好處:
1. 項目可測試更高,從而可以執行單元測試
2. 將UI和業務的設計完全分開,View和UnitTest只是ViewModel的兩個不同形式的消費者
3. 有助於我們區別並哪些是UI操作,哪些是業務操作,而不是將他們全混在CodeBehind中
3.項目結構介紹
以下是示例項目的結構截圖:
以下是各模塊之間的聯繫:
4.WpfMVVMSample.Foundation
提供一些基礎類定義。
5.Model的職責
Model主要提供基礎實體的屬性以及每個屬性的驗證邏輯。
Model不包含數據的調用,但是可以包含簡單的非數據調用的操作,如產生序列號或者合併字段。
對於WCF產生的客戶端代理類,Models中應有與之相對應的類結構定義。
Model不依賴於任何項目。
6.IService和Services以及ServiceTest
IService是所有網絡數據服務或者IO操作的服務接口。
IService中的數據訪問方式以異步爲主,見參考示例。
Service是真實的數據服務訪問類,是IService的實現。ServiceTest是用於測試ViewModel的IService的實現
7.ViewModel的職責
ViewModel是MVVM架構中最重要的部分,ViewModel中包含屬性,命令,方法,事件,屬性驗證等邏輯。爲了與View以及Model更好的交互來滿足MVVM架構,ViewModel的設計需要注意一些事項或者約束:
ViewModel的屬性:ViewModel的屬性是View數據的來源。這些屬性可由三部分組成:
一部分是Model的複製屬性。
另一部分用於控制UI狀態。例如一個彈出窗口的控件可能有一個IsClose的屬性,當操作完成時可以通過這個屬性更改通知View做相應的UI變換或者後面提到的事件通知。
第三部分是一些方法的參數,可以將這些方法的參數設置成相應的屬性綁定到View中的某個控件,然後在執行方法的時候獲取這些屬性,所以一般方法不含參數。
ViewModel的命令:ViewModel中的命令用於接受View的用戶輸入,並做相應的處理。我們也可以通過方法實現相同的功能。
ViewModel的事件: ViewModel中的事件主要用來通知View做相應的UI變換。它一般在一個處理完成之後觸發,隨後需要View做出相應的非業務的操作。所以一般ViewModel中的事件的訂閱者只是View,除非其他自定義的非View類之間的交互。
ViewModel的方法:有些事件是沒有直接提供命令調用的,如自定義的事件。這時候我們可以通過CallMethodAction來調用ViewModel中的方法來完成相應的操作。
8.View及Codebehind
View中使用Command:View中的Button等控件可以直接綁定Command屬性調用ViewModel中的Command
View中使用CallMethodAction :一些不支持Command的控件,可以用一個CallMethodAction觸發器來執行ViewModel中的方法。注意的是方法當中往往包含一些參數,這些參數一般可以通過給ViewModel設置相應的屬性來綁定到相關的輸入控件,如TextBox。
View中使用DataTrigger:除了模型屬性,還有一部分是狀態屬性,這往往是ViewModel通過屬性更改的方式通知View做出相關的UI操作,例如觸發一段動畫,或者切換控件狀態等等。這個時候可以使用一些觸發器,當狀態值不同時做出相應的UI變換。
View的CodeBehind中初始化子View的ViewModel上下文:View一般由父View調用,所以View的ViewModel一般由父View來初始化。比如當點擊人脈按鈕,需要顯示人脈的View的時候,就由主框架初始化人脈的ViewModel,並顯示人脈的View。
View的CodeBehind中訂閱子View的UI事件:除了通過狀態屬性的變更觸發View中的觸發器,看一種選擇是在View的CodeBehind中訂閱ViewModel的UI事件。
9.View及ViewModel交互模式總結
由以上解析我們可以總結出View和ViewModel的交互模式:
1. 父View在CodeBehind中初始化子ViewModel
2. 父View在CodeBehind中訂閱子ViewModel的UI事件
3. 父View將子ViewModel賦值給子View的DataContext,並顯示子View
4. 父View調用子ViewModel獲取數據的方法,子ViewModel調用數據服務獲取數據
5. ViewModel的數據通過Binding傳遞給View
6. View接受用戶輸入,並通過Command或者CallMethodAction交給ViewModel做業務處理
7. ViewModel處理完成之後觸發UI事件或者更改狀態屬性通知父View
8. 父View做出View變換至新的界面
10. 依賴注入
ViewModel的職責是提供數據給View,並調用底層的數據服務。
爲了解除ViewModel和BP的耦合,增加一層IService的接口定義,這也使得我們可以構造不同的IService實現來測試ViewModel。
但是View 在使用ViewModel的時候,ViewModel必須要使用IService對象,所以這裏採用依賴注入。通過依賴注入來完全解除View以及ViewModel對於Service的依賴。
示例項目源代碼下載: