2009-02-27 51 views
8

我正在Silverlight2中開發一個應用程序並嘗試遵循Model-View-ViewModel模式。我將一些控件上的IsEnabled屬性綁定到ViewModel上的布爾屬性。PropertyChanged計算屬性的通知

我遇到了這些屬性從其他屬性派生出來的問題。比方說,我有一個保存按鈕,我只希望在可以保存時啓用(數據已加載,而且我們目前不忙於在數據庫中執行任何操作)。

所以我有這樣幾個屬性:

private bool m_DatabaseBusy; 
    public bool DatabaseBusy 
    { 
     get { return m_DatabaseBusy; } 
     set 
     { 
      if (m_DatabaseBusy != value) 
      { 
       m_DatabaseBusy = value; 
       OnPropertyChanged("DatabaseBusy"); 
      } 
     } 
    } 

    private bool m_IsLoaded; 
    public bool IsLoaded 
    { 
     get { return m_IsLoaded; } 
     set 
     { 
      if (m_IsLoaded != value) 
      { 
       m_IsLoaded = value; 
       OnPropertyChanged("IsLoaded"); 
      } 
     } 
    } 

現在我想做的事情是這樣的:

public bool CanSave 
{ 
    get { return this.IsLoaded && !this.DatabaseBusy; } 
} 

但需要注意的缺乏屬性更改通知。

所以,問題是:什麼是暴露我可以綁定到一個布爾值屬性的一個乾淨的方式,而是計算被明確設置,並提供通知,以便用戶界面能正確地更新?

編輯:感謝大家的幫助 - 我得到了它,並去了一個自定義的屬性。如果有人感興趣,我會在此發佈源代碼。我相信它可以以更清晰的方式完成,所以如果您發現任何缺陷,請添加評論或答案。

基本上我所做的就是作出定義鍵 - 值對的列表,持什麼樣的性質取決於其他屬性的接口:

public interface INotifyDependentPropertyChanged 
{ 
    // key,value = parent_property_name, child_property_name, where child depends on parent. 
    List<KeyValuePair<string, string>> DependentPropertyList{get;} 
} 

我遂作出上述屬性去每個屬性:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)] 
public class NotifyDependsOnAttribute : Attribute 
{ 
    public string DependsOn { get; set; } 
    public NotifyDependsOnAttribute(string dependsOn) 
    { 
     this.DependsOn = dependsOn; 
    } 

    public static void BuildDependentPropertyList(object obj) 
    { 
     if (obj == null) 
     { 
      throw new ArgumentNullException("obj"); 
     } 

     var obj_interface = (obj as INotifyDependentPropertyChanged); 

     if (obj_interface == null) 
     { 
      throw new Exception(string.Format("Type {0} does not implement INotifyDependentPropertyChanged.",obj.GetType().Name)); 
     } 

     obj_interface.DependentPropertyList.Clear(); 

     // Build the list of dependent properties. 
     foreach (var property in obj.GetType().GetProperties()) 
     { 
      // Find all of our attributes (may be multiple). 
      var attributeArray = (NotifyDependsOnAttribute[])property.GetCustomAttributes(typeof(NotifyDependsOnAttribute), false); 

      foreach (var attribute in attributeArray) 
      { 
       obj_interface.DependentPropertyList.Add(new KeyValuePair<string, string>(attribute.DependsOn, property.Name)); 
      } 
     } 
    } 
} 

該屬性本身只存儲一個字符串。您可以爲每個屬性定義多個依賴項。該屬性的內容在BuildDependentPropertyList靜態函數中。你必須在你的類的構造函數中調用它。 (任何人都知道是否有方法通過類/構造函數屬性來完成此操作)在我的情況下,所有這些都隱藏在基類中,因此在子類中只需將屬性放在屬性中。然後你修改你的OnPropertyChanged等價物來查找任何依賴關係。以下是我的ViewModel基類:

public class ViewModel : INotifyPropertyChanged, INotifyDependentPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    protected virtual void OnPropertyChanged(string propertyname) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(propertyname)); 

      // fire for dependent properties 
      foreach (var p in this.DependentPropertyList.Where((x) => x.Key.Equals(propertyname))) 
      { 
       PropertyChanged(this, new PropertyChangedEventArgs(p.Value)); 
      } 
     } 
    } 

    private List<KeyValuePair<string, string>> m_DependentPropertyList = new List<KeyValuePair<string, string>>(); 
    public List<KeyValuePair<string, string>> DependentPropertyList 
    { 
     get { return m_DependentPropertyList; } 
    } 

    public ViewModel() 
    { 
     NotifyDependsOnAttribute.BuildDependentPropertyList(this); 
    } 
} 

最後,在受影響的屬性上設置屬性。我喜歡這種方式,因爲派生屬性擁有它所依賴的屬性,而不是其他方式。

[NotifyDependsOn("Session")] 
    [NotifyDependsOn("DatabaseBusy")] 
    public bool SaveEnabled 
    { 
     get { return !this.Session.IsLocked && !this.DatabaseBusy; } 
    } 

這裏需要注意的一點是,它只在其他屬性是當前類的成員時才起作用。在上面的示例中,如果this.Session.IsLocked更改,則通知無法通過。我解決這個問題的方法是訂閱this.Session.NotifyPropertyChanged併爲「Session」激發PropertyChanged。(是的,這將導致事件射擊那裏他們沒有需要)

回答

6

傳統的方式做,這是一個OnPropertyChanged調用添加到每個可能影響您計算一個屬性的,就像這樣:

public bool IsLoaded 
{ 
    get { return m_IsLoaded; } 
    set 
    { 
     if (m_IsLoaded != value) 
     { 
      m_IsLoaded = value; 
      OnPropertyChanged("IsLoaded"); 
      OnPropertyChanged("CanSave"); 
     } 
    } 
} 

這能有點亂(如果,例如,您在CanSave中的計算更改)。

一(清潔我不知道?)的方式來解決,這將是重寫OnPropertyChanged並在那裏進行電話:

protected override void OnPropertyChanged(string propertyName) 
{ 
    base.OnPropertyChanged(propertyName); 
    if (propertyName == "IsLoaded" /* || propertyName == etc */) 
    { 
     base.OnPropertyChanged("CanSave"); 
    } 
} 
2

您需要添加一個通知的CanSave性質變化無處不在的特性之一這取決於變化:

OnPropertyChanged("DatabaseBusy"); 
OnPropertyChanged("CanSave"); 

而且

OnPropertyChanged("IsEnabled"); 
OnPropertyChanged("CanSave"); 
+0

這將正常工作,但它意味着你將被無效CanSave超過必要的程度,可能會使應用程序的其他部分執行比所需更多的工作。 – KeithMahoney 2009-02-27 02:33:53

+0

如果我沒有訪問該屬性的設置方法,該怎麼辦? – geofftnz 2009-02-27 02:34:23

1

這個解決方案如何?

private bool _previousCanSave; 
private void UpdateCanSave() 
{ 
    if (CanSave != _previousCanSave) 
    { 
     _previousCanSave = CanSave; 
     OnPropertyChanged("CanSave"); 
    } 
} 

然後在IsLoaded和DatabaseBusy的setter中調用UpdateCanSave()?

如果你不能修改IsLoaded和DatabaseBusy的setter,因爲它們在不同的類中,你可以嘗試在定義IsLoaded和DatabaseBusy的對象的PropertyChanged事件處理函數中調用UpdateCanSave()。