C# 已更改链接只读属性的WPF INotifyProperty

C# 已更改链接只读属性的WPF INotifyProperty,c#,wpf,inotifypropertychanged,C#,Wpf,Inotifypropertychanged,我试图了解如果我有一个依赖于另一个属性的只读属性,如何更新UI,以便对一个属性的更改同时更新两个UI元素(在本例中为文本框和只读文本框)。例如: public class raz : INotifyPropertyChanged { int _foo; public int foo { get { return _foo; } set { _foo = value; onPropertyChanged(th

我试图了解如果我有一个依赖于另一个属性的只读属性,如何更新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。正确的方法是什么?

指示bar已更改的一种方法是在foo setter中添加对onPropertyChanged(此“bar”)的调用。我知道这很难看,但你有它


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

根据计算费用和您希望使用的频率,将其设置为私有集属性,并在设置
foo
时计算值,而不是在他在调用
bar
get时飞行。这基本上是一个缓存解决方案,然后您可以作为
bar
私有setter的一部分执行属性更改通知。我通常更喜欢这种方法,主要是因为我使用AOP(通过Postsharp)来实现实际的
INotifyPropertyChanged
样板文件

-丹

你只要打个电话就可以了

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");
        }
    }

如果这是一个严重的问题(所谓“严重”,我的意思是您有大量的依赖只读属性),您可以制作一个属性依赖关系映射,例如:

private static Dictionary<string, string[]> _DependencyMap = 
    new Dictionary<string, string[]>
{
   {"Foo", new[] { "Bar", "Baz" } },
};
这与在
Foo
setter中放置多个
OnPropertyChanged
调用本质上没有太大区别,因为您必须为添加的每个新依赖属性更新依赖关系映射

但它确实可以随后实现一个
属性changedependensonattribute
,并使用反射扫描类型并构建依赖关系映射。这样,您的属性看起来就像:

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

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


在WPF中,通常有很多方法来完成相同的事情。通常情况下,没有正确的方法,只有个人偏好。

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

在ContinousLinq中,这对于集合非常有效

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

public int Foo
{
    get
    { 
        return  m_fooExp.Value;   
    }
}
private selfupdateingexpression m_fooExp=new selfupdateingexpression(this,()=>Foo*Foo);
公共int-Foo
{
得到
{ 
返回m_fooExp.Value;
}
}

但不幸的是,我在表达式和Linq中缺乏基本的知识:(

我意识到这是一个老问题,但这是“NotifyPropertyChanged of linked properties”的第一个谷歌结果,所以我认为添加这个答案是合适的,这样就有了一些具体的代码

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

属性类:

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

    public DependsOnPropertyAttribute(string otherProperty)
    {
        Dependence = otherProperty;
    }
}
在我的基本视图模型中(所有其他WPF视图模型都从中继承):


我不明白你为什么认为对改变了的财产(这个“酒吧”)开火特别难看?我可以看出.bar是否依赖于3个属性,并且您在所有3个setter中都调用了该函数…但是他可以将计算从.bar的属性getter中移出,并将其移动到调用update方法的计算方法中。这很难看,因为您正在将属性B的正确行为所需的逻辑放置在属性a的setter中re可以是依赖于A值的任意数量的属性。必须修改A以满足使用A的其他属性的需要是不明显和不可缩放的。更干净的解决方案是采用某种方式来指示B依赖于A,并且当A改变时,B也应表示A值改变。第二个选项上面,听自己的属性更改事件就是这种风格,但听自己的事件也有点古怪。呵呵-我想你对丑陋代码的定义比我低…:P我已经学会了“欣赏”不是事件汤的UI逻辑…我不认为
foo
应该对rasing
Pro负责pertyChanged
for
bar
。这会起作用,但是如果这些属性在另一个类上呢?那么您必须以另一种方式实现它。我在另一个问题中回答了这个问题,在我的情况下,即使属性在同一个类或另一个类中,解决方案也是相同的,用户需要查看它并该程序也需要访问它,但听起来确实很有趣。当我需要重复一系列属性时,这似乎是一个更好的可扩展解决方案。订阅PropertyChanged事件并触发条形图更改通知正是PRISM在其命令式快速启动中所做的。他们的示例更好-if(propertyName==“价格”| | propertyName==“数量”| | propertyName==“发货”){this.NotifyPropertyChanged(“总计”);}你能展示一下你对
PropertyChangeDependsOn
系统的实现吗?唉,我只是断言实现它是可能的。实际上实现它并不是我需要做的事情。谢谢。我实现了
[AttributeUsage(AttributeTargets.Property,AllowMultiple = true)]
public class DependsOnPropertyAttribute : Attribute
{
    public readonly string Dependence;

    public DependsOnPropertyAttribute(string otherProperty)
    {
        Dependence = otherProperty;
    }
}
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; }
}