原文:Introduction to Model/View/ViewModel pattern for building WPF apps
http://blogs.msdn.com/b/johngossman/archive/2005/10/08/478683.aspx
作者:John Gossman(http://blogs.msdn.com/b/johngossman)
譯者:winter(http://winter-cn.cnblogs.com)
MVVM模式是MVC的一個變體,它主要為那些設計人員比傳統開發人員需要為視圖承擔更多責任的現代UI開發平台量身定製。總的來說,設計人員是那種更關注圖形和美感,並且不會像傳統程式員那樣寫那麼多程式的開發人員。設計多數情況會用像HTML或者XAML這樣聲明式語言給出,而且很多時候都是用Dreamweaver, Flash或則Sparkle這樣的所見即所得 (WYSIWYG)工具產生出來的。一言以蔽之,就是應用程式的UI部分跟後台資料和商務邏輯部分是使用的是不一樣的工具,不一樣的語言,並且由不同的人完成的。MVC原本的設計針對的是SmallTalk這樣的整個程式用同一種環境和語言構建的系統,Model/View/ViewModel是一個對MVC的改進,用以適應眾所周知的Web環境以及現在的Avalon開發(譯註:Avalon是WPF的codename,這篇blog發布較早,還沒有確定WPF這個名稱)。
Model/View/ViewModel還依賴一件事情:一個整體的資料繫結機制。稍後詳述。
Model的如MVC中所定義,就是資料或者商務邏輯,完完全全與UI無關,它儲存了狀態並且做問題領域中的處理。Model可以寫在代碼裡面或者用寫在關係表或者XML中的純資料來表示。
Model/View/ViewModel中的View表示可見元素,按鈕,表單,圖形或者GUI中更複雜的控制項,它會對快速鍵進行編碼,並且控制項自身會管理跟輸入裝置的互動——這在MVC中本該是Controller負責的(現代GUI環境中發生在Controller上的事情是很長的題外話……我傾向於認為它只是隱藏到後台了,它仍然存在,但是我們不需要像是1979年那樣考慮那麼多事情了)。View幾乎總是以聲明的方式去定義的,而且通常是某種工具產生的。因為這些工具和聲明式語言的天然特性,MVC編碼進它的View類裡面的某些檢視狀態不是很容易表示。例如,UI可能有多個互動的模式如"顯示狀態"和"編輯狀態",這些模式會改變控制項的行為或者可見的外觀,但是這些模式並不總是能夠用XAML來表達的(儘管觸發器是個不錯的起點)。我們稍後會解決這個問題。
這個時候就需要資料繫結登場了。在簡單得例子中,View被直接資料繫結到到Model。Model的一些部分只是簡單地通過單向綁定顯示到view當中。Model的另一些部分可以用雙向繫結資料的控制項編輯。例如,一個Model中的布爾值可以被資料繫結到一個CheckBox,或者TextBox的字串欄位。
然而在實踐中,僅僅非常少數的程式UI可以直接資料繫結到Model,特別是當Model是個已經存在的類或者資料格式,應用程式開發人員還沒法控制它的時候。UI可能希望做必須在代碼中才能完成的複雜操作,放在我們嚴格意義上的View的中不合理,而放在包含在Model中又顯得過度針對具體問題(也可能根本沒法弄進已經存在的Model)。最終我們需要一個地方放檢視狀態,像選擇和模式這些。
ViewModel就是專門用於處理這些任務的。這個名詞表示"視圖的模型",並且可以看做視圖的抽象,但是還提供了Model專用於給View做資料繫結的特定形態。在後一種角色上,ViewModel包含了能夠將Model類型轉換到View類型的資料轉換器,而且它包含了View可以用以跟模型互動的命令。
我將會拓展這些思路,並且在接下來的文章中重點描述如何綁定視圖到ViewModel的命令。但是理清這個模式最快捷的方式是提供一些執行個體:
上面的圖片展示了Sparkle UI的三個編輯面板。每個都是用Model/View/ViewModel模式開發的。最簡單的是最上的Library面板。Model是一個程式集的列表(每個都是System.Reflection.Assembly的執行個體),而每個程式集對應的是一個控制項的列表。View是我們我們的面板控制項以及一系列的Style和DataTemplate,它們把程式集列表顯示到一個ComboBox中,把控制項列表顯示在一個ListBox中。我們把ComboBox的標題直接資料繫結到程式集對象的name,讓ListBox中的清單項目從Control的name中取出它們要的文本。ViewModel有像是當前選中的程式集並且暴露出向情境中插入一個控制項的命令。Selection是ViewModel中幾乎最常用的組件,你可能會覺得奇怪為什麼selection沒有放在View中。這樣做的原因是很多view中的控制項需要基於單選來協作。比起與view中所有不同的控制項協作,ViewModel中很容易綁定到一個單項選擇的表示。在Library面板中,被選中的程式集決定了ComboBox中什麼被選中以及ListBox中顯示什麼樣的資料。此外,設計師可以自由決定切換到用ListBox顯示程式集,用ComboBox顯示控制項列表,而完全無須從原來的view裡面複製同選擇相關的邏輯。
Appearance面板則以Sparkle編輯地區中被選擇的形狀或者控制項為它的Model。View中有一個ListBox,它用來顯示當前選擇中有趣的屬性(至少有所有的Pen和Brush屬性),還有用來決定Brush或者Pen是簡單還是漸層等的按鈕,以及用來編輯顏色組件的色彩頻譜。ViewModel 包含了哪個屬性被選中,編輯漸層的時候哪個漸層過渡點被選中,映射顏色到文本值和色彩頻譜的資料轉換器,以及用於更換正在編輯的Pen和Brush的命令。在這種情況下,Model是Avalon提供給我們的,View可以被輕易被改為完全不同的別的東西,而ViewModel則提供了UI中可複用組件的完全不同表示。
最後一個例子是我們的Project面板,這裡Model是個MSBuild項目……又一次遇到了Model類是預先存在的情況。View是一個樹型控制項,捲動區域,並且包含內容菜單。ViewModel適配了並非為Avalon專門設計的MSBuild中的概念(且從命令列可以完美工作),這樣我們可以對它們資料繫結,ViewModel中仍然包含了選擇資訊和命令
一旦你對Model/View/ViewModel有所瞭解,任何UI問題都可以以它的名詞來表述。事實上,整個Sparkle UI都是用這個模式定義的。“編輯地區中被選擇的形狀或者控制項”是Appearance面板的的Model,它自己本身又是我們Scene editor的ViewModel中的概念。Sparkle內部Panel的布局,註冊的panel列表是它的Model,View則是以帶有splitter的Grid來定位視圖,以及ViewModel用以決定哪些panel當前可見,以及它們在哪個邏輯容器裡(編輯地區,左,右,底邊)。