2009-02-27 58 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()。