本文章是由機器翻譯。

設計模式

Model-View-ViewModel 的問題和解決方案

Robert McCarter

下載範例程式碼

Windows Presentation Foundation (WPF) 和 Silverlight 提供豐富的 API,建置現今的應用程式,但瞭解和套用 harmony 相互建置設計良好的所有 WPF 功能,並輕鬆地維護的應用程式可能會很困難。在您啟動?和撰寫應用程式權限的方法是什麼?

模型-檢視-ViewModel (MVVM) 設計模式說明建立 WPF 和 Silverlight 應用程式的常見方法。它 ’s 建置應用程式的功能強大的工具,並討論應用程式設計與開發人員的共通的語言。雖然 MVVM 真正有用的模式 ’s 還是相當小,misunderstood。

何時是適用的 MVVM 設計模式] 和 [時不需要?應該應用程式被結構方式?工時量 ViewModel 層,撰寫和維護,以及何種替代方案存在降低 ViewModel 層中的程式碼數量的?在模型中的相關的屬性處理方式 elegantly?如何應該公開至該檢視模式中的集合?其中應該 ViewModel 物件被執行個體化,並連結到模型物件嗎?

在本文中我說明 [ViewModel 的運作方式,並討論一些優點以及在程式碼中實作一個 ViewModel 相關的問題。我也帶領您完成的部份將檢視階層中的模型物件公開為文件管理員使用 ViewModel 具體範例。

模型]、 [ViewModel] 及 [檢視

我目前處理的每個 WPF 和 Silverlight 應用程式都有相同的高階元件設計。在模型之的應用程式的核心,很多的精力進入設計物件導向分析與設計 (OOAD) 的最佳作法。

為我模型會是的應用程式的核心代表最大和最重要的企業資產,因為它會擷取所有複雜的商業項目、 關聯性和它們的功能。

坐在 [模型] 是 [ViewModel。[ViewModel 的兩個主要的目標是以便輕鬆地可消耗的 XAML WPF/檢視模型,可以分隔或封裝模型,從 [檢視]。雖然實用的原因它們 ’re 有時中斷,這些都是絕佳的目標。

您建立了解使用者如何與較高的層次應用程式互動的 ViewModel。但是,它 ’s MVVM 設計模式,在 ViewModel 完全檢視不知道的重要部分。這允許互動設計工具和圖形演出者密切使用程式開發人員設計適合 ViewModel 時建立 [ViewModel 頂端的美麗、 功能使用者介面以支援其盡最大努力。在就另外 decoupling 檢視和 ViewModel 之間也會允許 ViewModel,以進行更多的單元,可測試性和可重複使用。

若要協助強制執行模型]、 [檢視] 和 [ViewModel 層之間的嚴格分離,我喜歡建置為個別的 Visual Studio 專案每個圖層。結合可重複使用公用主要可執行組件和任何單元測試專案 (您有足夠的這些,右?) 這可能會導致大量的專案和組件, 的 圖 1 所示。

圖 1 的 MVVM 應用程式的元件

提供大量專案,此嚴格分離的方法是大型專案很明顯地,最有用。只有一或兩個開發人員使用的小型應用程式,此嚴格的區隔的好處可能不超過所造成的不便的建立,設定和維護多個專案,因此只要分隔成不同的命名空間相同的專案中的程式碼可能會提供足夠的隔離以上。

撰寫和維護一個 ViewModel 不是輕而易舉的事,而且應該不進行少量。但是,最基本的問題的回答 — 當您考慮 MVVM 設計模式] 和 [時不需要 — 通常在您的網域模型中找到。

在大型的專案網域模型可能會很複雜,幾百個仔細設計 elegantly 一起運作的任何類型的應用程式包括 Web 的服務類別 WPF 或 ASP.NET 應用程式。模型可能包含數個一起,工作的組件,並在非常大型的組織網域模型是有時建置及維護的特製化的開發小組。

當您大型且複雜的網域模型時 ’s 幾乎都採用 ViewModel 層很有幫助。

手動,有時網域模型是簡單、 或許不超過細的層級上資料庫。可能會自動產生類別和它們經常實作 INotifyPropertyChanged。UI 通常是清單] 或 [框格與編輯表單,讓使用者操作基礎資料的集合。Microsoft 工具組一定已經非常擅於快速且輕鬆地建置這類的應用程式。

如果您的模型或應用程式屬於此類別,一個 ViewModel 可能會強制造成高額外負荷,而不充分 benefitting 您應用程式的設計。

話雖如此,即使是在這種情況下,[ViewModel 仍然可以提供值。就例如 [ViewModel 是實作復原功能的絕佳位置。此外,您可以選擇要 MVVM 用於應用程式的一部分 (例如文件的管理我稍後討論) 和 pragmatically 公開您的模型,直接到該檢視。

為何要使用一個 ViewModel?

如果一個 ViewModel 似乎適用於您的應用程式,仍有開始撰寫程式碼之前,已解答的問題。第一個是如何減少 Proxy 的屬性數目。

從模型提倡 MVVM 設計模式的 [檢視分開是模式的一個重要且有長寬。如此一來如果模型類別有需要公開在檢視中的 10 個屬性,[ViewModel 通常結束得到 10 的相同屬性,只需 Proxy 基礎的模型執行個體來呼叫。這些 Proxy 屬性通常會引發屬性變更事件,當設定來指示已變更屬性的檢視。

不是每個模型屬性必須有一個 ViewModel Proxy] 屬性,但必須公開在檢視中每個模型屬性通常會有 Proxy 屬性。Proxy 屬性通常看起來像這樣:

public string Description {
  get { 
    return this.UnderlyingModelInstance.Description; 
  }
  set {
    this.UnderlyingModelInstance.Description = value;
    this.RaisePropertyChangedEvent("Description");
  }
}

任何非一般的應用程式會有數萬或數百個模型需要公開給以這種方式 ViewModel 透過使用者的類別。 這是只是內建 MVVM 所提供的分隔。

撰寫這些 Proxy 屬性是枯燥,因此容易發生錯誤,因為引發屬性變更事件,尤其是需要字串,必須符合屬性的名稱並不會包含在任何自動程式碼重整。 若要消除這些 Proxy 事件,常見的解決方案是直接,公開 ViewModel 包裝函式從 [模型] 執行個體,然後有網域模型實作 INotifyPropertyChanged 介面:

public class SomeViewModel {
  public SomeViewModel( DomainObject domainObject ) {
    Contract.Requires(domainObject!=null, 
      "The domain object to wrap must not be null");
    this.WrappedDomainObject = domainObject;
  }
  public DomainObject WrappedDomainObject { 
    get; private set; 
  }
...

因此,[ViewModel 可以仍然命令和所需的檢視,而不會複製模型屬性或建立的 Proxy 屬性的許多其他屬性公開。 這種方法肯定有它的訴求範圍,尤其是模型類別已實作 INotifyPropertyChanged 介面。 必須實作這個介面的模型 isn’t 一定是不正確的項目,或甚至常見 Microsoft.NET Framework 2.0 與 Windows Form 應用程式,也是。 它不會弄亂總網域] 模型不過,wouldn’t 是適用於 ASP.NET 應用程式或服務的網域。

使用這種方式檢視會會相依於模型,但它 ’s 只透過資料繫結並不需要檢視專案的專案參考到模型專案的間接相依。 因此純粹只是實用的原因這種方法有時是很有用。

但是,這種方法不會違反 MVVM] 設計模式的精神,而且可以減少您引入稍後 (例如復原功能) 的新 ViewModel 特定功能的能力。 我發生造成公平的位元,重新作業的這種方法的案例。 假設不常見的情況下有 ’s 深的巢狀的屬性上的資料繫結的位置。 如果某 ViewModel 是目前的資料內容,而且該人員有的地址資料繫結可能會看起來像這樣:

{Binding WrappedDomainObject.Address.Country}

如果您需要導入地址物件上的額外 ViewModel 功能,需要移除 WrappedDomainObject.Address 資料繫結參考,並改用新 ViewModel 屬性。 這是有問題的因為 XAML 資料繫結 (且可能是資料內容) 的更新會硬測試。 此檢視是一個元件,doesn’t 有自動化和完整的迴歸測試。

動態內容

我的 Proxy 屬性激增的解決方案是使用新的.NET Framework 4] 和 [WPF 支援動態物件和動態方法分派。 後者讓您在執行階段決定如何處理讀取或寫入至不實際存在的屬性類別上。 這表示您可以消除所有手寫的 Proxy 屬性中 [ViewModel 時仍將封裝基礎的模型。 但是,請注意 Silverlight 4 並不支援動態屬性的繫結。

若要實作這項功能最簡單的方式是讓 ViewModel 的基底類別擴充新 System.Dynamic.DynamicObject 類別和覆寫 TryGetMember 和 TrySetMember 方法。 動態語言執行階段 (DLR) 這兩個方法時,會呼叫所參考的屬性不存在於類別上, 允許在執行階段判斷如何實作遺失的屬性類別。 結合少量的反映 ViewModel 類別可以動態 Proxy 屬性存取,為基礎的模型執行個體只需要幾行程式碼:

public override bool TryGetMember(
  GetMemberBinder binder, out object result) {

  string propertyName = binder.Name;
  PropertyInfo property = 
    this.WrappedDomainObject.GetType().GetProperty(propertyName);

  if( property==null || property.CanRead==false ) {
    result = null;
    return false;
  }

  result = property.GetValue(this.WrappedDomainObject, null);
  return true;
}

此方法會啟動使用反映來尋找基礎的模型執行個體上的屬性。 (如需相關的詳細資料請參閱 2007 年 6 月 「 CLR Out 」 資料行 「 的 倒影上反映 」)。如果模型 doesn’t 有這類屬性,方法就無法藉由傳回 false 和資料繫結失敗。 如果屬性方法使用屬性資訊擷取並傳回模型 ’s 屬性值。 這是更多的工作,比傳統的 Proxy 屬性 ’s 取得方法,但這是唯一您需要撰寫所有模型和所有的屬性的實作。

真正的威力的動態 Proxy 屬性是方法的在屬性 setters。 您可以在 TrySetMember,包含常見的邏輯,例如引發屬性變更事件。 程式碼看起來像這樣:

public override bool TrySetMember(
  SetMemberBinder binder, object value) {

  string propertyName = binder.Name;
  PropertyInfo property = 
    this.WrappedDomainObject.GetType().GetProperty(propertyName);

  if( property==null || property.CanWrite==false )
    return false;

  property.SetValue(this.WrappedDomainObject, value, null);

  this.RaisePropertyChanged(propertyName);
  return true;
}

一次,方法會啟動使用反映來抓取從基礎的模型執行個體屬性。 如果屬性 doesn’t 存在,或屬性都是唯讀方法失敗時,藉由傳回 false。 如果屬性存在於網域物件,屬性資訊用來設定模型屬性中。 然後您可以包含所有屬性 setters 的常見的任何邏輯。 這個範例程式碼中我只是引發屬性變更事件,我只要設定屬性,但是您可以輕易地執行更多。

封裝模型的挑戰之一是模型經常有什麼統一模組化語言呼叫衍生的屬性。 就例如某類別可能有生日屬性和衍生的時代屬性。 時代屬性為唯讀,並自動計算根據出生日期和目前日期的天數:

public class Person : DomainObject {
  public DateTime BirthDate { 
    get; set; 
  }

  public int Age {
    get {
      var today = DateTime.Now;
      // Simplified demo code!
      int age = today.Year - this.BirthDate.Year;
      return age;
    }
  }
...

當 [生日] 屬性變更時代屬性也可隱含變成因為年齡以數學方式衍生自出生日期。 因此當 [生日] 屬性設定,ViewModel 類別需要提高 [生日] 屬性和時代屬性的屬性變更事件。 與動態的 ViewModel 方法即可自動進行此 inter-property 關係模型中明確。

第一次,您必須擷取屬性關聯的自訂屬性:

[AttributeUsage(AttributeTargets.Property, AllowMultiple=true)]
public sealed class AffectsOtherPropertyAttribute : Attribute {
  public AffectsOtherPropertyAttribute(
    string otherPropertyName) {
    this.AffectsProperty = otherPropertyName;
  }

  public string AffectsProperty { 
    get; 
    private set; 
  }
}

我設定到 AllowMultiple 設為 True 可支援的案例屬性可能會影響多個屬性。 套用這個屬性到模型中,直接 codify 生日與時代之間的關係很簡單:

[AffectsOtherProperty("Age")]
public DateTime BirthDate { get; set; }

若要將這個新的模型中繼資料動態 ViewModel 類別內我現在更新 TrySetMember 方法的三個額外的行的程式碼,讓它看起來就像這樣:

public override bool TrySetMember(
  SetMemberBinder binder, object value) {
...
  var affectsProps = property.GetCustomAttributes(
    typeof(AffectsOtherPropertyAttribute), true);
  foreach(AffectsOtherPropertyAttribute otherPropertyAttr 
    in affectsProps)
    this.RaisePropertyChanged(
      otherPropertyAttr.AffectsProperty);
}

手中已經存在的 [反映] 屬性] 資訊 GetCustomAttributes 方法可以傳回有關模型屬性的 AffectsOtherProperty 的任何屬性。 然後程式碼只是進行屬性是引發屬性變更事件,每個迴圈。 因此現在的 ViewModel 透過 [生日] 屬性的變更自動引發生日及時代的屬性變更事件。

它 ’s 一定要知道是否您明確程式上動態 ViewModel 類別的屬性 (或,多可能,在特定模型的衍生 ViewModel 類別),DLR 就不會呼叫 TryGetMember 和 TrySetMember 方法,將改屬性直接呼叫。 在這種情況下會遺失這個自動的行為。 但是,程式碼可以很容易地重整,以便自訂屬性可以使用這項功能也。

let’s 傳回資料繫結的問題 ([ViewModel 是目前的 WPF 資料內容) 深的巢狀屬性,如下所示:

{Binding WrappedDomainObject.Address.Country}

使用動態 Proxy 屬性表示基礎的包裝的網域物件不再公開的讓資料繫結實際上會看起來像這樣:

{Binding Address.Country}

在這種情況下 [地址] 屬性會仍然直接存取基礎的模型地址執行個體。 但是,現在想導入地址周圍的 ViewModel 時您只需要加入新的屬性人員 ViewModel 類別上。 新的 [位址] 屬性是很簡單的:

public DynamicViewModel Address {
  get {
    if( addressViewModel==null )
      addressViewModel = 
        new DynamicViewModel(this.Person.Address);
    return addressViewModel;
  }
}

private DynamicViewModel addressViewModel;

沒有 XAML 資料繫結,必須變更,因為屬性仍會呼叫位址,但現在 DLR 會呼叫新的具體內容,而不是動態 TryGetMember 方法。 (請注意延遲的執行個體化,這個位址] 屬性中不是安全執行緒。 但是,只檢視應該存取 [ViewModel 和 WPF/Silverlight 檢視] 是單一執行緒,這並不是重要的考量)。

當模型實作 INotifyPropertyChanged 時,可以使用這個方法。 ViewModel 可以注意到這,而不選擇 Proxy 已變更屬性之事件。 在這種情況下從基礎的模型執行個體接聽它們,然後 re-raises 自己為事件。 動態 ViewModel 類別的建構函式,在我可以執行檢查,請記住結果:

public DynamicViewModel(DomainObject model) {
  Contract.Requires(model != null, 
    "Cannot encapsulate a null model");
  this.ModelInstance = model;

  // Raises its own property changed events
  if( model is INotifyPropertyChanged ) {
    this.ModelRaisesPropertyChangedEvents = true;
    var raisesPropChangedEvents = 
      model as INotifyPropertyChanged;
    raisesPropChangedEvents.PropertyChanged +=
      (sender,args) => 
      this.RaisePropertyChanged(args.PropertyName);
  }
}

若要避免重複的屬性變更事件,我也需要讓 TrySetMember 方法稍微修改。

if( this.ModelRaisesPropertyChangedEvents==false )
  this.RaisePropertyChanged(property.Name);

因此您可以使用動態 Proxy 屬性來大幅簡化 ViewModel 層,藉由消除標準的 Proxy 屬性。這會大幅降低程式碼撰寫、 測試、 文件和長期維護。不會再將新的屬性加入至模型時,需要更新 ViewModel 層,除非有非常特殊的檢視邏輯,新的屬性。此外,這種方法可以解決困難的問題,像是相關的屬性。常見的 TrySetMember 方法也可以協助您實作的復原能力,因為使用者導向的屬性會變更所有流程透過 TrySetMember 方法。

優缺點

許多開發人員都 leery 反映 (以及 DLR),因為的效能問題。在我自己的工作我 haven’t 發現這會造成問題。效能產生負面影響的使用者設定在 UI 中的單一屬性時不可能被發現。可能無法如 multi-touch 設計表面的高度互動式使用者介面中的大小寫。

只有主要的效能問題時檢視的初始的母體中有大量的欄位數。使用性考量自然應該限制,以便透過此 DLR 方法的初始資料繫結的效能是無法偵測到公開任何畫面的欄位數目。

不過,效能應該永遠會仔細監視並了解當它與相關使用者經驗。先前所述的簡單方法,都可以重新撰寫以反映快取。其他的詳細資訊,請參閱 Joel Pobar ’s 文章MSDN Magazine 2005 年 7 月發行中。

還有一些有效性至程式碼的可讀性的引數和可維護性受到負面影響使用這種方法,因為檢視層似乎參考上 [ViewModel don’t 實際存在的屬性。但是,我相信優點排除大部分的手編碼 Proxy 屬性遠超過特別是與 [ViewModel 上的適當文件的問題。

動態 Proxy 屬性的方法不會減少或消除能夠模糊化模型層,因為在 XAML 中的名稱現在參考的模型屬性。使用傳統的 Proxy 屬性並不會限制您模糊化模型,因為屬性直接參考,而且會模糊化與其他應用程式的能力。但是,如大部分的模糊化工具還不適用於 XAML/BAML,這是主要無關。程式碼 cracker 可以從 XAML/BAML 開始,並入模型層,在任何情況下運作。

最後,這種方法可能會由 attributing 模型 
properties 與安全性相關的中繼資料,而且必須是為負責強制執行安全性 ViewModel 濫用。安全性 doesn’t 似乎檢視特定的責任,我相信這置於 [ViewModel 太多的責任。在這種情況下會更適合套用在模型中的外觀導向方法。

Collections

集合是其中一個最困難的且最令人滿意方面 MVVM 設計模式。如果 [模型會變更基礎模型中的集合,它 ’s 以某種方式公開變更,以便檢視本身可以適當地更新 ViewModel 的責任。

不幸的是,在所有的可能性模型不公開實作 INotifyCollectionChanged 介面的集合。在 [.NET] Framework 3.5 此介面會處於強會阻礙模型中的使用的 System.Windows.dll。幸運的是,[.NET] Framework 4] 中這個介面有移轉到 System.dll] 中太多進行更多的自然,若要使用從可觀察在模型中的集合。

顯著的集合模型中開啟模型開發新的可能性,並可以使用 Windows Form 和 Silverlight 應用程式中。這是目前我慣用的方法因為 ’s 比任何其他,更簡單,而且我 ’m 快樂 INotifyCollectionChanged 
interface 移至較常見的組件。

沒有可觀察模型中的集合,可以完成的最佳就是一些其他機制公開 (Expose) — 很可能是自訂的事件 — 在 [模型],以指出集合已變更時。您可以在特定模型的方式完成。就例如如果某類別有地址的集合它可以如公開事件:

public event EventHandler<AddressesChangedEventArgs> 
  NewAddressAdded;
public event EventHandler<AddressesChangedEventArgs> 
  AddressRemoved;

這最好引發專為 WPF ViewModel 設計的自訂集合事件。 但是,’s 仍然很難公開 (Expose) 中 [ViewModel 集合變更它。 可能是,唯一 recourse 是引發上整個 ViewModel 集合屬性的屬性變更事件。 這是在最令人滿意的解決方案。

集合的另一個問題決定時,或以包裝內 ViewModel 執行個體集合中的每個模型的執行個體。 對於較小的集合 [ViewModel 可能新的顯著集合公開 (Expose) 和複製的所有項目 ViewModel 顯著集合為基礎的模型集合中換行中相對應的 ViewModel 執行個體集合中的每個模型項目,因為它會。 ViewModel 可能需要接聽傳輸至基礎模型的使用者變更集合變更的事件。

但是,對於非常大型的集合,會公開在某種形式的 virtualizing 面板,最簡單且最實用的方法是只為了直接公開模型物件。

ViewModel 具現化

以很少所討論的 MVVM 設計模式的另一個問題在於何處及何時 ViewModel 執行個體應該被例項化。 這個問題是也經常被忽略,例如 MVC 類似的設計模式的討論區。

我喜好設定是撰寫 ViewModel 單一物件所提供的 [檢視可以輕鬆地擷取所需的所有其他 ViewModel 物件的主要 ViewModel 物件。 通常此母片的 ViewModel 物件提供命令實作,因此 [檢視可以支援文件的開頭。

但是,我使用過的應用程式的大部份提供一個文件為中心介面通常使用 Visual Studio 的類似的索引標籤式工作區。 因此 ViewModel 層中我想把文件,而言,文件會公開一或多個特定的模型物件文繞圖的 ViewModel 物件。 ViewModel 層中的標準 WPF 命令可以使用保存層擷取所需的物件、 ViewModel 執行個體中自動換行,並建立 ViewModel 來顯示它們的文件管理員。

在範例應用程式與此發行項包含,ViewModel] 指令來建立新的連絡人是:

internal class OpenNewPersonCommand : ICommand {
...
  // Open a new person in a new window.
  public void Execute(object parameter) {
    var person = new MvvmDemo.Model.Person();
    var document = new PersonDocument(person);
    DocumentManager.Instance.ActiveDocument = document;
  }
}

ViewModel 文件管理員所參考的最後一行是管理所有開啟 ViewModel 文件的單一物件。但問題是如何確實 ViewModel 文件的集合取得公開在檢視?

內建的 WPF 的索引標籤控制項並不會提供功能強大的多重文件介面,使用者已前來預期的類型。幸運的是,協力廠商停駐] 和 [索引標籤式工作區產品都可以。大部分的努力模擬包括分割檢視、 Ctrl +] 索引標籤的快顯視窗 (具有 mini-document 檢視) 和多 」 可停駐的工具視窗的 Visual Studio 的索引標籤式文件的外觀。

不幸的是,其中多數元件 don’t MVVM 設計模式提供內建支援。但是,因為您可以輕易地套用配接器的設計模式,以連結協力廠商檢視元件 ViewModel 文件管理員 ’s 確定。

文件管理員介面卡

確保在 圖 2 所示的介面卡設計 [ViewModel doesn’t 要求檢視的任何參考,它會檢查 MVVM 設計模式的主要目標。(不過,在本例中的文件概念被定義 ViewModel 層,而不是在模型層因為它 ’s 純粹只是一個使用者介面的概念)。

圖 2 的 文件管理員檢視配接器

ViewModel 文件管理員負責維護開啟 ViewModel 文件的集合,並了解哪些文件目前使用中。這項設計允許 ViewModel 圖層,以開啟和關閉使用 [文件] 管理員的文件,並變更此檢視的任何不知情的情況下使用中的文件。ViewModel 側邊,這種方法是相當容易的。的 圖 3 顯示 ViewModel 類別,在範例應用程式。

圖 3 的 ViewModel 層 ’s 文件管理員和文件類別

文件的基底類別會公開幾個內部的生命週期方法 (啟動、 LostActivation 和 DocumentClosed) 是由文件管理員来保留文件的最新相關什麼進行呼叫。文件也會實作 INotifyPropertyChanged 介面,讓它可以支援資料繫結。就例如介面卡資料繫結至 ViewModel ’s DocumentTitle 屬性檢視文件 ’s [標題] 屬性。

這種方法最複雜的部分是介面卡] 類別,然後我提供專案所附本文中的工作複本。配接器會訂閱文件管理員中的事件,並會使用這些事件來保持最新狀態區上的索引標籤控制項。就例如當文件管理員會指示在開啟新的文件,配接器接收事件,換行 ViewModel 文件中任何 WPF 控制項是必要的然後會公開在索引標籤式的工作區中的該控制項。

介面卡具有一個其他的責任:保持 ViewModel 文件管理員使用者 ’s 動作與同步處理。配接器必須因此也接聽事件從索引標籤式的工作區控制項,讓使用者變更使用中的文件,或關閉文件時,介面卡可以通知文件管理員。

無此邏輯時非常複雜,有一些警告。有幾個案例,其中程式碼就會變成 re-entrant,而這必須適當地處理。就例如如果 [ViewModel 會使用文件管理員關閉文件,配接器會接收文件管理員中的事件,並關閉實體的文件] 視窗在檢視中。這會使索引標籤式的工作區] 控制項,也會引發也會收到配接器的一個文件結尾] 事件和配接器 ’s 事件處理常式會的就說通知文件管理員應該關閉文件。文件已經關閉,所以文件管理員必須 sympathetic 允許這。

其他的困難度是檢視 ’s 介面卡必須要能夠連結檢視索引標籤式文件控制項與 ViewModel 文件物件。最強大的解決方案是使用一個 WPF 附加相依性屬性。配接器會宣告用來連結到其 ViewModel 文件執行個體的檢視視窗控制項的私用附加相依性屬性。

此發行項的 [範例] 專案中,我會使用稱為 AvalonDock,讓我附加相依性屬性,看起來像是 的 [圖 4] 所示的程式碼的開放原始碼索引標籤式的工作區元件。

圖 4 連結 ViewModel 文件與檢視控制

private static readonly DependencyProperty 
  ViewModelDocumentProperty =
  DependencyProperty.RegisterAttached(
  "ViewModelDocument", typeof(Document),
  typeof(DocumentManagerAdapter), null);

private static Document GetViewModelDocument(
  AvalonDock.ManagedContent viewDoc) {

  return viewDoc.GetValue(ViewModelDocumentProperty) 
    as Document;
}

private static void SetViewModelDocument(
  AvalonDock.ManagedContent viewDoc, Document document) {

  viewDoc.SetValue(ViewModelDocumentProperty, document);
}

新視窗控制項基礎 ViewModel 文件上時,配接器會建立新的檢視視窗控制項,設定附加的屬性 (請參閱 的 圖 5)。 您也可以查看此處,設定標題資料繫結和配接器如何設定資料內容和檢視文件控制項的內容,請參閱。

圖 5 設定附加的屬性

private AvalonDock.DocumentContent CreateNewViewDocument(
  Document viewModelDocument) {

  var viewDoc = new AvalonDock.DocumentContent();
  viewDoc.DataContext = viewModelDocument;
  viewDoc.Content = viewModelDocument;

  Binding titleBinding = new Binding("DocumentTitle") { 
    Source = viewModelDocument };

  viewDoc.SetBinding(AvalonDock.ManagedContent.TitleProperty, 
    titleBinding);
  viewDoc.Closing += OnUserClosingDocument;
  DocumentManagerAdapter.SetViewModelDocument(viewDoc, 
    viewModelDocument);

  return viewDoc;
}

藉由設定檢視文件控制 ’s 內容,我讓 WPF 執行大量 lifting,找出如何顯示 ViewModel 文件的這個特定類型。實際的資料範本,ViewModel 文件是主要的 [XAML] 視窗所包含的資源字典中。

我 WPF 和 Silverlight 成功使用此 ViewModel 文件管理員方法。只檢視層程式碼是在的配接器,這可以輕易地測試,然後離開單獨。這種方法會將 ViewModel 完全獨立的檢視,和我有上一次切換廠商為我的索引標籤式的工作區元件配接器類別中只有最少的變更與絕對沒有變更 ViewModel 或模型。

能夠使用 ViewModel 層中的文件覺得優雅,並實作 ViewModel 指令要示範了下面是簡單的一個。ViewModel 文件類別也變得明顯 ICommand 文件相關的執行個體公開 (Expose) 的地方。

攔截到這些指令的 [檢視,並透過身手的 MVVM 設計模式的優點。此外,ViewModel 文件管理員方法也適用於單一方法如果您需要公開 (Expose) 資料之前已經建立 (可能是在可摺疊的工具視窗中) 中的任何文件的使用者。

總結

MVVM 設計模式是一個功能強大且實用] 模式,但沒有設計模式可以解決每個問題。我這裡示範,將 MVVM 模式和目標結合與其他的模式,例如介面卡,並同時也利用如動態的分派的新.NET Framework 4 功能的 singletons 可以協助解決許多常見的考量,周圍實作 [MVVM 設計模式。正確的方法採用 MVVM 讓更簡潔和易於維護的 WPF 和 Silverlight 應用程式。進一步閱讀有關 MVVM,請參閱 Josh Smith ’s 文章MSDN Magazine 的二月 2009年問題中。

Robert McCarter* 是加拿大 freelance 軟體開發人員、 架構設計人員和位。讀取他的部落格,在 robertmccarter.wordpress.com 。*

多虧了要檢閱這份文件的下列的技術專家:  Josh 史密斯