我正在Silverlight2中开发一个应用程序,并尝试遵循Model-View-ViewModel模式。我将某些控件上的IsEnabled属性绑定到ViewModel上的布尔属性。
当这些属性从其他属性派生时,我遇到了问题。假设我有一个Save按钮,我只想在可以保存的情况下启用它(数据已加载,我们当前并没有忙于在数据库中执行操作)。
所以我有几个这样的属性:
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; }
}
但请注意,缺少属性更改通知。
所以问题是:什么是暴露我可以绑定的单个布尔属性的简洁方法,但是计算而不是显式设置并提供通知,以便UI可以正确更新?
编辑: 感谢大家的帮助 - 我明白了,并开始制作自定义属性。我在这里发布消息来源以防任何人感兴趣。我确信它可以以更清洁的方式完成,所以如果您发现任何缺陷,请添加评论或答案。
基本上我做了一个界面,它定义了一个键值对列表,以保存依赖于其他属性的属性:
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。 (是的,这会导致事件发生在他们不需要的地方)
答案 0 :(得分: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");
}
}
答案 1 :(得分:2)
您需要在其所依赖的某个属性更改的位置添加CanSave属性更改的通知:
OnPropertyChanged("DatabaseBusy");
OnPropertyChanged("CanSave");
和
OnPropertyChanged("IsEnabled");
OnPropertyChanged("CanSave");
答案 2 :(得分:1)
这个解决方案怎么样?
private bool _previousCanSave;
private void UpdateCanSave()
{
if (CanSave != _previousCanSave)
{
_previousCanSave = CanSave;
OnPropertyChanged("CanSave");
}
}
然后在IsLoaded和DatabaseBusy的setter中调用UpdateCanSave()?
如果你不能修改IsLoaded和DatabaseBusy的setter,因为它们在不同的类中,你可以尝试在PropertyChanged事件处理程序中调用UpdateCanSave()来定义IsLoaded和DatabaseBusy。