简体   繁体   English

WPF INotifyPropertyChanged用于链接的只读属性

[英]WPF INotifyPropertyChanged for linked read-only properties

I am trying to understand how to update the UI if I have a read-only property that is dependent on another property, so that changes to one property update both UI elements (in this case a textbox and a read-only textbox. For example: 我试图了解如何更新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));
    }
  }
}

My understanding is that bar will not automatically update the UI when foo is modified. 我的理解是,当修改foo时,bar不会自动更新UI。 Whats the correct way to do this? 这是正确的方法吗?

I realize this is an old question, but it's the first Google result of "NotifyPropertyChanged of linked properties", so I think it's appropriate to add this answer so that there's some concrete code. 我意识到这是一个老问题,但它是“NotifyPropertyChanged of linked properties”的第一个Google结果,所以我认为添加这个答案是合适的,以便有一些具体的代码。

I used Robert Rossney's suggestion and created a custom attribute, then used it in a base view model's PropertyChanged event. 我使用了Robert Rossney的建议并创建了一个自定义属性,然后在基本视图模型的PropertyChanged事件中使用它。

The attribute class: 属性类:

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

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

And in my base view model (which all other WPF view models inherit from): 在我的基本视图模型中(所有其他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));
        }
    }
}

This now allows me to mark properties easily, like so: 这现在允许我轻松标记属性,如下所示:

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

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

One way to indicate that bar has changed is to add a call to onPropertyChanged(this, "bar") in the foo setter. 指示bar已更改的一种方法是在foo setter中添加对onPropertyChanged(this, "bar")的调用。 Ugly as hell, I know, but there you have it. 我知道,丑陋,但你有它。

If foo is defined in an ancestor class or you otherwise don't have access to the implementation of the setter, I suppose you could subscribe to the PropertyChanged event so that when you see a "foo" change, you can also fire a "bar" change notification. 如果foo是在祖先类中定义的,或者你没有访问setter的实现,我想你可以订阅PropertyChanged事件,这样当你看到“foo”更改时,你也可以触发一个“bar” “改变通知。 Subscribing to events on your own object instance is equally ugly, but will get the job done. 在您自己的对象实例上订阅事件同样很难看,但是会完成工作。

If this is a serious issue (by "serious", I mean you have a non-trivial number of dependent read-only properties), you can make a property dependency map, eg: 如果这是一个严重的问题(通过“严肃”,我的意思是你有一个非平凡数量的依赖只读属性),你可以创建一个属性依赖关系图,例如:

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

and then reference it in OnPropertyChanged: 然后在OnPropertyChanged中引用它:

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

This isn't inherently a lot different from just putting multiple OnPropertyChanged calls in the Foo setter, since you have to update the dependency map for every new dependent property you add. 这与在Foo setter中放置多个OnPropertyChanged调用本身并没有多大区别,因为您必须为添加的每个新依赖属性更新依赖关系映射。

But it does make it possible to subsequently implement a PropertyChangeDependsOnAttribute and use reflection to scan the type and build the dependency map. 但它确实可以随后实现PropertyChangeDependsOnAttribute并使用反射来扫描类型并构建依赖关系图。 That way your property would look something like: 这样你的财产看起来像:

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

You could simply call 你可以简单地打电话

OnPropertyChanged(this, "bar");

from anywhere in this class...You cold even go like this: 从这个班级的任何地方......你甚至冷如此:

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

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

If you are only using bar for UI purposes, you could remove it from your model completely. 如果您仅将UI用于UI目的,则可以将其从模型中完全删除。 You could bind the UI element to the foo property and use a custom value converter to change the result from foo into foo*foo. 您可以将UI元素绑定到foo属性,并使用自定义值转换器将结果从foo更改为foo * foo。

In WPF there are often a lot of ways to accomplish the same thing. 在WPF中,通常有很多方法可以完成同样的事情。 Often times, there isn't a correct way, just a personal preference. 通常情况下,没有正确的方法,只有个人偏好。

Depending on the expense of the calculation and how frequently you expect it to be used, it may be beneficial to make it a private set property and calculate the value when foo is set, rather than calculating on the fly when the bar get is called. 根据计算的费用以及您期望使用它的频率,将其设置为私有set属性并在设置foo时计算值可能是有益的,而不是在调用bar get时动态计算。 This is basically a caching solution and then you can do the property change notification as part of the bar private setter. 这基本上是一个缓存解决方案,然后您可以将属性更改通知作为bar私有设置器的一部分。 I generally prefer this approach, mainly because I use AOP (via Postsharp) to implement the actual INotifyPropertyChanged boilerplate. 我通常更喜欢这种方法,主要是因为我使用AOP(通过Postsharp)来实现实际的INotifyPropertyChanged样板。

-Dan -担

I pretty sure it must be possible in a declarative way, but my first attempt to solve this issue have been a failure. 我很确定它必须以声明的方式实现,但我第一次尝试解决这个问题却是失败的。 The solution i want to accomplish is using Lambda Expressions to define this. 我想要完成的解决方案是使用Lambda Expressions来定义它。 Since Expressions can be parsed (??) it should be possible to parse the expressions and attach to all NotifyPropertyChanged events to get notified about depended data changes. 由于可以解析表达式(??),因此应该可以解析表达式并附加到所有NotifyPropertyChanged事件,以获得有关依赖数据更改的通知。

in ContinousLinq, this works excellent for collections. 在ContinousLinq中,这对于集合非常有用。

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

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

but unfortionatly i lack on fundamentional know how in Expressions and Linq :( 但不幸的是我缺乏基本知识如何在表达式和Linq :(

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM