[转载]MVVM、MVVMLight、MVVMLight Toolkit之我见
本文轉自 陳希章-MVVM、MVVMLight、MVVMLight Toolkit之我見
MVVM、MVVMLight、MVVMLight Toolkit之我見
我想,現在已經有不少朋友在項目中使用了MVVMLight了吧,如果你正在做WPF,Silverlight,Windows Phone的開發,那么,你有十分必要的理由了解MVVM和MVVMLight。我寫這篇文章的目的,是給大家做一個總結,以便更多的朋友了解并掌握MVVM。
?
首先,要說一下MVVM的概念。MVVM嚴格來說,并不是一種框架,而是一個設計的模式吧。與它有關的設計模式還有MVC (現在廣泛用于Web應用中),以及MVP(之前有用過在Windows Forms和WPF中)
?
如果你希望對MVVM有更加感性地認識,我推薦你看下面這篇文章。
http://www.codeproject.com/KB/WPF/WpfMvvmQuickStart.aspx
這篇文章寫得實在太好了,我很欣賞這樣的技術人才,能把一個抽象問題有層次地講清楚。(我強烈建議對MVVM的概念了解不深的朋友,認真讀這篇文章,而不要急于用MVVMLight,因為MVVM是一種模式,而MVVMLight只是其中一種具體的實現)
?
然后,我要說一下MVVMLight吧,剛才說了,它是一種MVVM的實現。自然它不是唯一的一種實現,但現在大家公認的是,它是比較好的一個實現。就我個人的體會來說,我以前用過微軟提供的Prism中的MVVM特性,但老實說,可能Prism的目標太大了,所以在MVVM這個具體的點上,實在不是那么好用。
?
值得一說的是,從使用Prism轉換到使用MVVMLight過程相當簡單,如果有類似情況的朋友,不要有什么顧慮。我這里不是說Prism不好,它與Mvvmlight嚴格來說,不是一個重量級的產品。MVVMLight專注與MVVM的實現,自然更加靈活。
?
接下來,我認為要學習MVVMLight最好的Quick start,就是作者自己寫的這個網頁
http://galasoft.ch/mvvm/
通過這個文章,我們可以很清楚地了解MVVMLight的設計思路和包含的有關組件,無需太多補充,文章淺顯易懂,確實是我們要學習的一個榜樣
請注意,我這篇文章并非逐一講解MVVMLight的細節功能使用,我主要提一些重點,并且分享一些我的看法和觀點,當然這僅是我一家之言,不見得完全正確。
事實上針對如何使用的方面,已經有不少文章了,大家可以參考
http://zzk.cnblogs.com/s?w=mvvmlight
另外一方面,我覺得大家其實要自己多動手才會有實際的收獲。MVVMLight使用并不難,在使用中大家可以領會到更多。
?
實際上,我們經常談論MVVMLight的時候,其實談的是MVVMLight Toolkit,它主要是為了更加方便開發人員使用MVVMLight,它會在本地的GAC(Global Assembly Cache)中分別安裝針對WPF,Silverlight,Windows Phone的Assembly(分別各自有兩個Assembly),并且在Visual Studio中添加相應的項目以及項模板,更加貼心的一點是,它還提供了幾個代碼段。
?
有的朋友可能會問,那么MVVMLight到底是什么呢?呃,MVVMLight嘛,就是MVVMLight Toolkit的名稱啦 ,有點繞對吧,放松點,不要那么較真嘛
?
?
這里要指出的是,我個人并不喜歡用這個Toolkit提供的項目模板和代碼段。我覺得它所生成的代碼有些冗余,修改起來反而麻煩。我真正的項目中就不用這些模板,而是傾向于自己編寫ViewModel之類的。這可能也跟我之前并不是一開始就使用MVVMLight有關系,我更習慣自己寫那些代碼,我指的是一些ViewModel的屬性,命令和綁定等等。
?
好吧,我承認,我為什么不喜歡使用Toolkit的模板呢?還有一個原因是,除非必要,我對于工具要給Visual Studio添加額外的東西(哪怕是有用的),總是很敏感的,我擔心它讓Visual Studio變慢。是的,你知道,這多少有點類似“潔癖”的嫌疑,但怎么說呢,讓我保留自己這個權利吧
?
那么,如果你像我一樣,不安裝MVVMLight Toolkit,如何使用MVVMLight呢?實際上很簡單,我更加習慣于使用nuget package 來獲取最新的MVVMLight的Library,并將它們添加到項目中來。
你可以通過這個菜單打開nuget package explorer,如下圖所示,然后,你可以在Online里面搜索MVVMLight,或者像我這樣在Recent package中直接就可以Install。(nuget package是會被緩存在本地的,所以即便沒有鏈接到網絡,也可以正常使用)
既然可以緩存在本地,那么其實和安裝到GAC是沒有太大區別的,不是嗎?
而且用這種方式還有一個好處,你總是可以得到最新的版本,因為nuget package是自動有更新提示的。而如果你是用Toolkit的話,則得不到更新的提示。(據可靠消息,MVVMLight將很快有4.0這個版本)
?
很好,你現在已經知道如何將MVVMLight添加到項目中,接下來就是該讓它發揮威力的時候啦。大家一定要理解MVVM的兩個核心目標
1.讓UI界面與邏輯能夠很好地分離又協同工作。
2.讓邏輯代碼更具有可測試性。
?
我們先來說說分離并協同這個目標,在MVVMLight中主要通過什么實現的呢?它提供了ViewModelBase這個基類,可以讓我們很方便地編寫ViewModel。從下面的截圖可以看出,它提供了很多有用的特性,例如判斷是否在設計狀態(IsInDesignMode),以及觸發屬性更改通知(RaisePropertyChanged),尤其是后者,這可以說是MVVM的根基,為什么這么說呢?UI與邏輯的分離并且協同工作,關鍵就在于WPF和Silverlight有強大的數據綁定機制,而數據綁定機制之所以能夠強大,就是因為WPF和Silverlight中引入的依賴屬性(Dependency Property)的機制,而依賴屬性,區別于普通屬性的最重要一點就是既可以有單向綁定,也可以有雙向綁定,而且屬性更改之后,可以通知到所有綁定目標上面。
?
除了很好的支持綁定,UI與邏輯分離并協作的另外一個重要機制,就是命令(command)機制。在MVVMLight中,它提供了兩個基本的命令:RelayCommand和RelayCommand<T>
這兩個命令其實沒有本質區別,只不過后者是支持泛型的一個參數的,就是可以從命令源接受參數數據。
需要注意的是,這兩個命令只適合綁定在基于按鈕的Click事件上面。例如Button,HyperlinkButton是最常見的。例如下面的例子
這個綁定的意思,其實就是說,當這個Button被點擊了之后,調用ViewModel中的SaveCommand
如果需要傳遞參數過去呢,就是下面這樣啦。我舉了兩個例子,第一個例子參數是一個常數,而第二個例子參數是一個綁定值,這都是允許的
但問題是,如果我要綁定其他事件呢?例如MouseMove事件,該怎么辦呢?在MvvmLight.Extras這個程序集里面,單獨又給出了一個Command綁定方式,叫EventToCommand,顧名思義,它可以將任何事件綁定到一個命令
要使用這個略微麻煩一些,請看下面的例子
所以,綁定(尤其是雙向綁定)和命令是MVVM的精髓,但實際要認真講起來,MVVMLight這方面實現得其實也沒有什么特別突出的,其他一些框架也都是這么做的。以前沒有這些框架之前,我們也是這么寫的,無非是代碼會多一些而已。
有童鞋可能會說了,屬性綁定我們可以理解,但為嘛要這么麻煩去綁定命令呢?直接在xaml.cs里面寫不就完了嗎?請注意,MVVM的一個目標就是讓xaml.cs代碼中盡量少,極端的情況是沒有任何用戶代碼。這樣才能實現UI與邏輯的分離,所以盡可能地用Command來做。
這里我也分享我的個人經驗,一定會有的時候,你沒有辦法全部用Command,而不在xaml.cs中寫任何代碼。那個時候,你也大可像我一樣,將代碼寫一些在xaml.cs中也無妨。典型的情況,是希望在視圖里面接受消息(下面就要講到),并且更新界面的一些效果,例如啟動動畫。這里面是一個度的把握,并無絕對的好壞。我已經看到有人心領神會地點頭了,所謂隨機應變,大家要有一定的靈活性。
?
不過,Mvvmlight的一個創造性的設計,是它的Message(消息)機制,它讓View和ViewModel,以及ViewModel之間通訊變得相當方便,甚至神奇。我相當欣賞這個設計,這是Mvvmlight之所以稱為Mvvmlight的原因。
具體來說,它提供了一個Messenger類型,可以用來發送和接收消息,它還提供了默認的幾種消息類型。
A Messenger class (and diverse message types) to be used to communicate within the application. Recipients only receive the message types that they register for. Additionally, a target type can be specified, in which case the message will only be transmitted if the recipient's type matches the target parameter. Messages can be anything from simple values to complex objects. You can also use specialized message types, or create your own types deriving from them. More information about the Messenger class.?
- MessageBase: A simple message class, carrying optional information about the message's sender.?
- GenericMessage<T>: A simple message with a Content property of type T.?
- NotificationMessage: Used to send a notification (as a string) to a recipient. For example, save your notifications as constant in a Notifications class, and then send Notifications.Save to a recipient.?
- NotificationMessage<T>: Same as above, but with a generic Content property. Can be used to pass a parameter to the recipient together with the notification.?
- NotificationMessageAction: Sends a notification to a recipient and allows the recipient to call the sender back.?
- NotificationMessageAction<T>: Sends a notification to a recipient and allows the recipient to call the sender back with a generic parameter.?
- DialogMessage: Used to request that a recipient (typically a View) displays a dialog, and passes the result back to the caller (using a callback). The recipient can choose how to display the dialog, either with a standard MessageBox, with a custom popup, etc…?
- PropertyChangedMessage<T>: Used to broadcast that a property changed in the sender. Fulfills the same purpose than the PropertyChanged event, but in a less tight way.
一個稍微具體一點的例子,請參考
我非常喜歡這個Messenger的功能,但同時,我個人覺得它的設計有值得改進之處,首先它的語法有點繁瑣了,不是嗎?
我們顯然更希望用下面這樣的語法
這是如何實現的呢,其實我是自己對Messenger做了一個擴展
using System; using System.Collections.Generic; using System.Linq; using System.Text; using GalaSoft.MvvmLight.Messaging;namespace WpfApplication1 {/// <summary>/// 對默認的Messenger做擴展,以便更加易于使用/// 作者:陳希章/// </summary>public static class MessengerExtension{public static void Send<T>(this IMessenger messenger, T body, object token){Messenger.Default.Send<GenericMessage<T>>(new GenericMessage<T>(body), token);}public static void Register<T>(this Messenger messenger, object recipient, object token, Action<T> action){Messenger.Default.Register<GenericMessage<T>>(recipient, token, msg => {action(msg.Content);});}} }?
關于Messenger,其次我還覺得,它定義那么多消息類型,并不是非常理想,容易把使用者搞暈(我其實也不是很理解為什么既要做一個GenericMessage,還有一個NotificationMessage等等)。這也是我用上面這樣的方式擴展的原因。我后面會整理一個擴展代碼,做成可以分享的package給大家使用。
?
講了這么多,其實還有一個經常被大家忽視的目標:可測試性。這是很重要的。如何理解MVVM的可測試性,以及在MVVMLight中的具體實現呢?
我們來看一個例子,我們通常會說這是一個不可測試的代碼例子
?
為什么說它是不可測試的呢?因為我們都知道,MessageBox是需要人去響應的,你要點擊一下才會被關閉掉。而我們的測試(包括單元測試),大多都是要能批量,自動運行的,那么遇到這種MessageBox怎么辦呢?
我們一般單元測試代碼會這么樣寫
運行起來之后,它確實會按照預期的那樣去執行代碼,很顯然它會彈出一個對話框,讓我們去點擊
點擊了之后,當然測試會通過。但問題是,如果測試還需要人工干預才能運行,顯然不利于自動化。
我們來看在MVVMLight中如何解決這個問題的。我們得捋一下思路:你的目的是要彈出一個對話框(或者類似的東西),但如果你必須用MessageBox的話,就肯定是會彈出那個對話框來。有什么辦法可以解決這個問題呢?
答案就是:MvvmLight提供的Messenger機制。我們來看如下的例子
那么,這個消息會被誰來響應呢?一般是在View里面去響應,仔細想想:顯示消息(以及如何顯示)其實是View的責任,與ViewModel沒有什么關系。
很好,這樣就是MVVM的做法了,那么我們再來運行測試看看會怎么樣呢?大家如果自己運行一下就知道了,測試直接通過了,沒有任何消息提示。
等等,這難道就說明我們做對了嗎?我們的測試中,怎么確認消息發出去了呢?也就是說,既然上面的代碼并不會彈出消息,你怎么確認那個方法里面發送了消息呢?
所以,好戲一般都在后頭,不要著急下結論。所以可測試性,是指MvvmLight為此類問題都準備了解決方案。我們如何確認SaveCommand里面肯定調用而且僅僅調用了一次Messenger.Send方法呢?
很顯然,我們得有一個什么方式,模擬Messenger的功能:我們并不真的去發消息,我們是要驗證發送消息的方法真的被調用,而且發的消息內容是不是“保存成功”,這就是我們測試的目的。
在這里,我們會用到一個模擬的框架,我最喜歡用的是Moq這個框架。這也是一個開源項目,它的官方網站是 http://code.google.com/p/moq/
同樣,我們可以通過nuget package explorer中獲取它,實在是很方便,不是嗎?
?
然后,我們編寫下面的測試代碼
using WpfApplication1; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Windows.Input;using Moq; using GalaSoft.MvvmLight.Messaging;namespace TestProject1 {/// <summary>///This is a test class for MainWindowViewModelTest and is intended///to contain all MainWindowViewModelTest Unit Tests///</summary>[TestClass()]public class MainWindowViewModelTest{/// <summary>///A test for SaveCommand///</summary>[TestMethod()]public void SaveCommandTest(){MainWindowViewModel target = new MainWindowViewModel(); // TODO: Initialize to an appropriate value var messenger = new Mock<Messenger>();messenger.Setup(m => m.Send(It.Is<DialogMessage>(d => d.Content == "保存成功"))).Verifiable();Messenger.OverrideDefault(messenger.Object);var cmd = target.SaveCommand;cmd.Execute(null);messenger.Verify();}} }?
上面的代碼很好理解,我們希望驗證Messenger的Send方法是否被調用,而且發送的消息是不是一個DialogMessage,內容是不是“保存成功”。moq的特點就是語義很通俗易懂,讓我們為它鼓掌。
再次運行測試的話,我們會發現這次也還是正常通過了測試。但如果,我們將ViewModel方法里面的那句發送消息的代碼注釋掉,則就會報告一個錯誤
我們甚至還可以驗證Send方法調用了多少次,諸如此類,這是moq的功能,這里就不多展開了。
?
?
寫在最后的話
感謝Laurent Bugnion 的杰出工作,他是微軟MVP,我也看過他的視頻,講解MVVMLight及其原理和使用的,蠻平易近人的,典型的程序員和技術發燒友吧。有一個視頻上面,他穿的一間黑色T恤,上面就寫著幾個字:geek, 極客,你懂的
Laurent還將源代碼發布到了Codeplex,你可以通過下面這里下載到
http://mvvmlight.codeplex.com/
轉載于:https://www.cnblogs.com/LCHL/archive/2013/05/07/3065933.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的[转载]MVVM、MVVMLight、MVVMLight Toolkit之我见的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大数据项目实战之数据采集
- 下一篇: 专利学习笔记5:CPC客户端的安装方法