2015-10-19 77 views
1

我有一個問題,我有麻煩的措辭正確,因此一直沒能找到一個令人滿意的答案呢。也許這裏的某個人可以指引我走向正確的方向。WPF DataBinding之間ViewModels

我在WPF中使用MVVM來創建類似於UML的建模工具。對於所有的意圖和目的,讓我們堅持UML類比。

我基本上有4個相關的ViewModels:CanvasViewModel,ClassViewModel,MemberViewModel,TypeViewModel。它們都實現了暴露bool有效屬性的相同接口。你可以想象,畫布上有1個全局畫布,n個類,一個類中有n個成員,每個成員有1個類型。它可以表示像這樣的:

Canvas 
{ 
    Person (Class) 
    { 
    Age (Member) 
    { 
     int (Type) 
    } 
    Name (Member) 
    { 
     string (Type) 
    } 
    } 
    House (Class) 
    { 
    Price (Member) 
    { 
     currency (Type) 
    } 
    } 
} 

它實現了畫面像這樣:(http://www.tutorialspoint.com/uml/images/uml_class_diagram.jpg

我綁定CanvasViewModel.Valid到畫布上,如果它是顯示一個紅色的大探照燈假。 如果它是false,我將ClassViewModel.Valid綁定到類框以執行擺動動畫。 我將MemberViewModel.Valid綁定到列表視圖,如果它爲false,則呈現紅色。 我特別將TypeViewModel.Valid綁定到任何東西。

當然,有效屬性被實現非常簡單(代碼未測試):

// CanvasViewModel 
public bool Valid { get { return Classes.All(x => x.Valid); } } 

// ClassViewModel 
public bool Valid { get { return Members.All(x => x.Valid); } } 

// MemberViewModel 
public bool Valid { get { return Type.Valid; } } 

所以用例在此是:用戶不小心設置TypeViewModel爲「innt」,這是一個無效的類型。我希望所有4個ViewModel都將其有效屬性評估爲false。我想讓這個事件通過所有正確的ViewModel傳播(從Type - > Member - > Class - > Canvas)。

如何在地球上我做到這一點,而無需到處折騰我的代碼與可怕的線條如

var memberViewModel = new MemberViewModel(member); 
memberViewModel.PropertyChanged += (o, e) => { if (e.PropertyName == "Valid") OnPropertyChanged("Valid"); } 
Members.Add(memberViewModel); 

我不希望我的串在一起的功能是這樣。我更喜歡使用乾淨的綁定,具有清晰的入口/出口,獲取/設置或添加/刪除功能。

所以是的,不是ViewModel和GUI之間的綁定,而是ViewModels之間的綁定。我試着玩弄OnPropertyChanged和CoerceValue。但他們的目的並不完全清楚。好像在我的情況下,會員OnPropertyChanged的實現應該是這樣的

public static void OnPropertyChanged(...) 
{ 
    MyParentClass.CoerceValue(ClassViewModel.ValidProperty); 
} 

我想這不會是太糟糕了,唯獨我不知道什麼責任CoerceValue居然有這樣看來。評估是否在那裏進行?該屬性是否承擔了CoerceValue的返回值? OnPropertyChanged表示「set」,CoerceValue表示常規Property中的「get」,這是否簡單?無論如何,我沒有得到它的工作,所以我懷疑我正確理解它的目的。所有使用OnPropertyChanged/CoerceValue的示例基本上都是在同一個類中處理DependencyProperties(永遠不會跨類)。

任何想法如何解決這個相當普遍的問題?

乾杯,

勒內

+1

最終,它是關於屬性更改通知傳播以及訂閱上游PropertyChanged事件的類,並將它們在它們自己的PropertyChanged事件中傳播到下游類。你可以使註冊合理優雅,但除非你想限制你的設計以至於你可以擁有「一個班級來統治所有人」,否則沒有簡單的方法。 –

回答

1

處理這種情況的設計模式討論得很好here。 在你的情況下,我喜歡選項1,在那裏你持有對每個孩子的父對象的引用。然後,您將調用屬性設置器中從子項更改的父項屬性。這可能是最簡單的解決方案,但它確實引入了父 - 子耦合,但似乎並不像你所描述的那樣具有同樣的擔憂。我會建議將IValidating傳遞給子構造函數,並在setter中檢查它是否爲null。如果您使用mvvm框架,您還可以查看消息總線模式

+0

消息總線被提及的事實證實了我的懷疑,即沒有真正優雅的方式來做到這一點。感謝您指點我正確的方向! – Rene

0

使用反射和接口(或某種基類)。使用基類來標識要爲其設置值的對象的類型,並迭代到每個類中。一些效果如下:

interface IValid 
    { 
     bool Valid { get; set; } 
    } 

    class Canvas : IValid, INotifyPropertyChanged 
    { 
     private bool valid; 
     public bool Valid 
     { 
      get { return this.valid; } 
      set 
      { 
       this.valid = value; 
       this.OnPropertyChanged("Valid"); 

       this.SetPropertyValues(this); 
      } 
     } 


     public Canvas() 
     { 
      this.Person = new Person(); 
     } 

     public Person Person { get; set; } 


     public event PropertyChangedEventHandler PropertyChanged; 

     private void OnPropertyChanged(string propertyName) 
     { 
      var handler = this.PropertyChanged; 
      if (handler != null) 
      { 
       handler(this, new PropertyChangedEventArgs(propertyName)); 
      } 
     } 

     private void SetPropertyValues(IValid obj) 
     { 
      var properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); 

      foreach (var property in properties) 
      { 
       if (property.CanRead) 
       { 
        var validObject = property.GetValue(obj) as IValid; 

        if (validObject != null) 
        { 
         validObject.Valid = obj.Valid; 

         SetPropertyValues(validObject); 
        } 
       } 
      } 
     } 


    } 
    class Person : IValid, INotifyPropertyChanged 
    { 
     private bool valid; 
     public bool Valid 
     { 
      get { return this.valid; } 
      set 
      { 
       this.valid = value; 
       this.OnPropertyChanged("Valid"); 
      } 
     } 


     public Person() 
     { 
      this.Age = new Age(); 
     } 

     public Age Age { get; set; } 



     public event PropertyChangedEventHandler PropertyChanged; 

     private void OnPropertyChanged(string propertyName) 
     { 
      var handler = this.PropertyChanged; 
      if (handler != null) 
      { 
       handler(this, new PropertyChangedEventArgs(propertyName)); 
      } 
     } 
    } 

    class Age : IValid, INotifyPropertyChanged 
    { 
     private bool valid; 
     public bool Valid 
     { 
      get { return this.valid; } 
      set 
      { 
       this.valid = value; 
       this.OnPropertyChanged("Valid"); 
      } 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 

     private void OnPropertyChanged(string propertyName) 
     { 
      var handler = this.PropertyChanged; 
      if (handler != null) 
      { 
       handler(this, new PropertyChangedEventArgs(propertyName)); 
      } 
     } 
    }