依赖于其他属性的依赖项属性

时间:2012-02-20 13:07:37

标签: wpf dependency-properties

C类实现了INotifyPropertyChanged。

假设C具有长度,宽度和面积属性,其中Area = Length * Width。 中的更改可能会导致区域更改。这三个都是绑定的,即UI期望所有三个都通知其值的变化。

当“长度”或“宽度”发生变化时,其设置者将调用NotifyPropertyChanged。

我该如何处理计算出的Area属性?目前我能想到的模式是在NotifyPropertyChanged中检测更改的属性是长度还是宽度,如果是这种情况,则启动Area的附加PropertyChanged通知。但是,这需要我在NotifyPropertyChanged内部维护依赖关系图,我认为这是一种反模式。

所以,我的问题是:我应该如何编写依赖于其他依赖项属性的依赖项属性?

编辑:此处的人员建议长度和宽度也为区域调用NotifyPropertyChanged。我再次认为这是一种反模式。一个属性(恕我直言)不应该知道谁依赖它,不应该NotifyPropertyChanged。只有财产应该知道它依赖谁。

5 个答案:

答案 0 :(得分:3)

这个问题一直困扰着我,所以我重新打开它。

首先,我想为任何接受我个人“反模式”评论的人道歉。这里提供的解决方案确实是在WPF中完成的。然而,恕我直言,他们是不良做法造成的,缺乏框架。

我的主张是information hiding指南规定,当B依赖于A时,A不应该知道B.例如,当B 来自 A时,A不应该有代码说:“如果我的运行时类型确实是B,那么就这样做吧。”同时,当B 使用 A时,A不应该有代码说:“如果调用此方法的对象是B,那么......”

因此,如果属性B依赖于属性A,则A不应该是负责直接警告B的人。

相反,维护(正如我现在所做的)NotifyPropertyChanged中的依赖关系图也是一种反模式。该方法应该是轻量级的,并执行其命名状态,而不是维护属性之间的依赖关系。

所以,我认为所需的解决方案是通过aspect oriented programming:Peroperty B应该使用“I-depend-on(Property A)”属性,而某些代码重写器应该创建依赖图并透明地修改NotifyPropertyChanged

今天,我是一个从事单一产品工作的程序员,所以我再也无法证明这一点,但我认为这是正确的解决方案。

答案 1 :(得分:3)

这篇文章描述了如何创建一个自定义属性,该属性根据另一个属性自动调用PropertyChanged属性:http://www.redmountainsw.com/wordpress/2012/01/17/a-nicer-way-to-handle-dependent-values-on-propertychanged/

代码如下所示:

[DependsOn("A")]
[DependsOn("B")]
public int Total
{
  get { return A + B; }
}

public int A 
{
  get { return m_A; }
  set { m_A = value; RaisePropertyChanged("A"); }
}

public int B
{
  get { return m_B: }
  set { m_B = value; RaisePropertyChanged("B"); }
}

我自己没试过,但我喜欢这个想法

答案 2 :(得分:0)

LengthWidth属性发生更改时,您为PropertyChanged 激发Area,以便为Length触发它}或Width

这是一个基于支持字段的简单实现,以及用于触发OnPropertyChanged事件的方法PropertyChanged

public Double Length {
  get { return this.length; }
  set {
    this.length = value;
    OnPropertyChanged("Length");
    OnPropertyChanged("Area");
  }
}

public Double Width {
  get { return this.width; }
  set {
    this.width = value;
    OnPropertyChanged("Width");
    OnPropertyChanged("Area");
  }
}

public Double Area {
  get { return this.length*this.width; }
}

这样做肯定是反模式。这正是这样做的模式。作为类的实现者,您知道在更改Length时,Area也会更改,并通过引发相应的事件对其进行编码。

答案 3 :(得分:0)

然后你应该在长度和宽度属性设置器中加注两次。一个用于实际属性,一个用于Area属性。

例如:

private int _width;
public int Width
{
    get { return _width; }
    set
    {
        if (_width == value) return;
        _width = value;
        NotifyPropertyChanged("Width");
        NotifyPropertyChanged("Area");
    }
}
  

这里的人建议长度和宽度也可以调用   区域的NotifyPropertyChanged。我再次认为这是一个   反模式。财产(恕我直言)不应该知道谁依赖   它,不应该NotifyPropertyChanged。只有财产应该   意识到它依赖于谁。

这不是反模式。实际上,你的数据封装在这个类中,所以这个类知道什么时候和什么改变了。你不应该知道这个类之外的区域取决于宽度和长度。因此,通知侦听器有关Area的最合理的位置是Width和Length setter。

  

财产(恕我直言)不应该知道谁依赖它,如   不应该NotifyPropertyChanged。

它不会破坏封装,因为您在同一个类中,在相同的数据结构中。

额外的信息是knockout.js(一个javascript mvvm库)有一个概念来访问这个问题:Computed Observables。所以我相信这绝对是可以接受的。

答案 4 :(得分:0)

以下是属性的可能实现:

public class DependentPropertiesAttribute : Attribute
{
    private readonly string[] properties;

    public DependentPropertiesAttribute(params string[] dp)
    {
        properties = dp;
    }

    public string[] Properties
    {
        get
        {
            return properties;
        }
    }
}

然后在Base View Model中,我们处理调用属性依赖的机制:

public class ViewModelBase : INotifyPropertyChanged
{
    public ViewModelBase()
    {
        DetectPropertiesDependencies();
    }

    private readonly Dictionary<string, List<string>> _dependencies = new Dictionary<string, List<string>>();

    private void DetectPropertiesDependencies()
    {
        var propertyInfoWithDependencies = GetType().GetProperties().Where(
        prop => Attribute.IsDefined(prop, typeof(DependentPropertiesAttribute))).ToArray();

        foreach (PropertyInfo propertyInfo in propertyInfoWithDependencies)
        {
            var ca = propertyInfo.GetCustomAttributes(false).OfType<DependentPropertiesAttribute>().Single();
            if (ca.Properties != null)
            {
                foreach (string prop in ca.Properties)
                {
                    if (!_dependencies.ContainsKey(prop))
                    {
                        _dependencies.Add(prop, new List<string>());
                    }

                    _dependencies[prop].Add(propertyInfo.Name);
                }
            }
        }
    }

    protected void OnPropertyChanged(params Expression<Func<object>>[] expressions)
    {
        expressions.Select(expr => ReflectionHelper.GetPropertyName(expr)).ToList().ForEach(p => {
            RaisePropertyChanged(p);
            RaiseDependentProperties(p, new List<string>() { p });
        });

    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    protected virtual void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    protected void RaiseDependentProperties(string propertyName, List<string> calledProperties = null)
    {
        if (!_dependencies.Any() || !_dependencies.ContainsKey(propertyName))
            return;

        if (calledProperties == null)
            calledProperties = new List<string>();

        List<string> dependentProperties = _dependencies[propertyName];

        foreach (var dependentProperty in dependentProperties)
        {
            if (!calledProperties.Contains(dependentProperty))
            {
                RaisePropertyChanged(dependentProperty);
                RaiseDependentProperties(dependentProperty, calledProperties);
            }
        }
    }
}

最后,我们在ViewModel

中定义依赖项
[DependentProperties("Prop1", "Prop2")]
public bool SomeCalculatedProperty
{
    get
    {
        return Prop1 + Prop2;
    }
}