WPF INotifyPropertyChanged用于链接的只读属性

时间:2010-08-26 18:38:40

标签: c# wpf inotifypropertychanged

我试图了解如何更新UI,如果我有一个依赖于另一个属性的只读属性,那么对一个属性的更改会更新两个UI元素(在这种情况下是一个文本框和一个只读文本框)例如:

public class raz : INotifyPropertyChanged
{

  int _foo;
  public int foo
  {
    get
    {
      return _foo;
    }
    set
    {
      _foo = value;
      onPropertyChanged(this, "foo");
    }
  }

  public int bar
  {
    get
    {
      return foo*foo;
    }
  }

  public raz()
  {

  }

  public event PropertyChangedEventHandler PropertyChanged;
  private void onPropertyChanged(object sender, string propertyName)
  {
    if(this.PropertyChanged != null)
    {
      PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
    }
  }
}

我的理解是,当修改foo时,bar不会自动更新UI。什么是正确的方法?

7 个答案:

答案 0 :(得分:12)

我意识到这是一个老问题,但它是“链接属性的NotifyPropertyChanged”的第一个Google结果,所以我认为添加这个答案是合适的,以便有一些具体的代码。

我使用了Robert Rossney的建议并创建了一个自定义属性,然后在基本视图模型的PropertyChanged事件中使用它。

属性类:

[AttributeUsage(AttributeTargets.Property,AllowMultiple = true)]
public class DependsOnPropertyAttribute : Attribute
{
    public readonly string Dependence;

    public DependsOnPropertyAttribute(string otherProperty)
    {
        Dependence = otherProperty;
    }
}

在我的基本视图模型中(所有其他WPF视图模型都继承自):

public abstract class BaseViewModel : INotifyPropertyChanged
{
    protected Dictionary<string, List<string>> DependencyMap;

    protected BaseViewModel()
    {
        DependencyMap = new Dictionary<string, List<string>>();

        foreach (var property in GetType().GetProperties())
        {
            var attributes = property.GetCustomAttributes<DependsOnPropertyAttribute>();
            foreach (var dependsAttr in attributes)
            {
                if (dependsAttr == null)
                    continue;

                var dependence = dependsAttr.Dependence;
                if (!DependencyMap.ContainsKey(dependence))
                    DependencyMap.Add(dependence, new List<string>());
                DependencyMap[dependence].Add(property.Name);
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler == null)
            return;

        handler(this, new PropertyChangedEventArgs(propertyName));

        if (!DependencyMap.ContainsKey(propertyName))
            return;

        foreach (var dependentProperty in DependencyMap[propertyName])
        {
            handler(this, new PropertyChangedEventArgs(dependentProperty));
        }
    }
}

现在允许我轻松标记属性,如下所示:

public int NormalProperty
{
    get {return _model.modelProperty; }
    set 
    {
        _model.modelProperty = value;
        OnPropertyChanged();
    }
}

[DependsOnProperty(nameof(NormalProperty))]
public int CalculatedProperty
{
    get { return _model.modelProperty + 1; }
}

答案 1 :(得分:8)

指示条形图已更改的一种方法是在foo setter中添加对onPropertyChanged(this, "bar")的调用。我知道,这很丑陋,但你有它。

如果foo是在祖先类中定义的,或者你没有访问setter的实现,我想你可以订阅PropertyChanged事件,这样当你看到“foo”更改时,你也可以触发一个“酒吧”更改通知。在您自己的对象实例上订阅事件同样很难看,但是可以完成工作。

答案 2 :(得分:8)

如果这是一个严重的问题(“严重”,我的意思是你有一个非平凡数量的从属只读属性),你可以创建一个属性依赖关系图,例如:

private static Dictionary<string, string[]> _DependencyMap = 
    new Dictionary<string, string[]>
{
   {"Foo", new[] { "Bar", "Baz" } },
};

然后在OnPropertyChanged中引用它:

PropertyChanged(this, new PropertyChangedEventArgs(propertyName))
if (_DependencyMap.ContainsKey(propertyName))
{
   foreach (string p in _DependencyMap[propertyName])
   {
      PropertyChanged(this, new PropertyChangedEventArgs(p))
   }
}

这与在OnPropertyChanged setter中放置多个Foo调用本身并没有多大区别,因为您必须为添加的每个新依赖属性更新依赖关系图。

但它确实可以随后实现PropertyChangeDependsOnAttribute并使用反射来扫描类型并构建依赖关系图。这样你的财产看起来像:

[PropertyChangeDependsOn("Foo")]
public int Bar { get { return Foo * Foo; } }

答案 3 :(得分:6)

您可以直接致电

OnPropertyChanged(this, "bar");

从这个班级的任何地方......你甚至冷如此:

    public raz()
    {
        this.PropertyChanged += new PropertyChangedEventHandler(raz_PropertyChanged);
    }

    void raz_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if(e.PropertyName == "foo")
        {
             onPropertyChanged(this, "bar");
        }
    }

答案 4 :(得分:3)

如果您仅将UI用于UI目的,则可以将其从模型中完全删除。您可以将UI元素绑定到foo属性,并使用自定义值转换器将结果从foo更改为foo * foo。

在WPF中,通常有很多方法可以完成同样的事情。很多时候,没有正确的方法,只是个人偏好。

答案 5 :(得分:0)

取决于计算的费用以及您期望使用它的频率,将其设置为私有集属性并在设置foo时计算值可能是有益的,而不是动态计算调用bar get时这基本上是一个缓存解决方案,然后您可以将属性更改通知作为bar私有设置器的一部分。我通常更喜欢这种方法,主要是因为我使用AOP(通过Postsharp)来实现实际的INotifyPropertyChanged样板。

-Dan

答案 6 :(得分:0)

我很确定它必须以声明的方式存在,但我第一次尝试解决这个问题却是失败的。 我想要完成的解决方案是使用Lambda Expressions来定义它。 由于可以解析表达式(??),因此应该可以解析表达式并附加到所有NotifyPropertyChanged事件,以获得有关依赖数据更改的通知。

在ContinousLinq中,这非常适合收藏。

private SelfUpdatingExpression<int> m_fooExp = new SelfUpdatingExpression<int>(this, ()=> Foo * Foo);

public int Foo
{
    get
    { 
        return  m_fooExp.Value;   
    }
}

但遗憾的是我缺乏表达式和Linq中的基本知识:(