INotifyPropertyChanged and calculated property

天大地大妈咪最大 提交于 2019-12-28 16:02:09

问题


Suppose I have simple class Order, that have a TotalPrice calculated property, which can be bound to WPF UI

public class Order : INotifyPropertyChanged
{
  public decimal ItemPrice 
  { 
    get { return this.itemPrice; }
    set 
    {
       this.itemPrice = value;
       this.RaisePropertyChanged("ItemPrice");
       this.RaisePropertyChanged("TotalPrice");
    }
  }

  public int Quantity 
  { 
    get { return this.quantity; }
    set 
    {
       this.quantity= value;
       this.RaisePropertyChanged("Quantity");
       this.RaisePropertyChanged("TotalPrice");
    }
  }

  public decimal TotalPrice
  {
    get { return this.ItemPrice * this.Quantity; }    
  }
}

Is it a good practice to call RaisePropertyChanged("TotalPrice") in the properties that affect to TotalPrice calculation? What is the best way to refresh TotalPrice property? The other version to do this of course is to change property like this

public decimal TotalPrice
{
    get { return this.ItemPrice * this.Quantity; } 
    protected set 
    {
        if(value >= 0) 
            throw ArgumentException("set method can be used for refresh purpose only");

    }
}

and call TotalPrice = -1 instead of this.RaisePropertyChanged("TotalPrice"); in other properties. please suggest solutions better

Thanks a lot


回答1:


It's fine to check to see if you should raise this event as well from any other member that may change the value, but only do so if you actually change the value.

You could encapsulate this in a method:

private void CheckTotalPrice(decimal oldPrice)
{
    if(this.TotalPrice != oldPrice)
    {
         this.RaisePropertyChanged("TotalPrice");
    }
}

Then you need to call that from your other mutating members:

var oldPrice = this.TotalPrice;
// mutate object here...
this.CheckTotalPrice(oldPrice);



回答2:


Another solution is the one Robert Rossney proposed in this question:

WPF INotifyPropertyChanged for linked read-only properties

You can create a property dependency map (using his code samples):

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

and then do this in your OnPropertyChanged:

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

You can even attach an attribute to tie the dependent property to the one it depends on. Something like:

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

I haven't implemented the details of the attribute yet. I'd better get to working on that now.




回答3:


If you use NotifyPropertyWeaver you can have this code

public class Order : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public decimal ItemPrice { get; set; }

    public int Quantity { get; set; }

    public decimal TotalPrice
    {
        get { return ItemPrice*Quantity; }
    }
}

And it will be compiled to this.

public class Order : INotifyPropertyChanged
{
    decimal itemPrice;
    int quantity;
    public event PropertyChangedEventHandler PropertyChanged;

    public virtual void OnPropertyChanged(string propertyName)
    {
        var propertyChanged = PropertyChanged;
        if (propertyChanged != null)
        {
            propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public decimal ItemPrice
    {
        get { return itemPrice; }
        set
        {
            if (itemPrice != value)
            {
                itemPrice = value;
                OnPropertyChanged("TotalPrice");
                OnPropertyChanged("ItemPrice");
            }
        }
    }

    public int Quantity
    {
        get { return quantity; }
        set
        {
            if (quantity != value)
            {
                quantity = value;
                OnPropertyChanged("TotalPrice");
                OnPropertyChanged("Quantity");
            }
        }
    }

    public decimal TotalPrice
    {
        get { return ItemPrice*Quantity; }
    }
}



回答4:


Is it a good practice to call RaisePropertyChanged("TotalPrice") in the properties that affect to TotalPrice calculation?

No, it's not, it doesn't scale and (the fact that property should know everything that depends on it) is a maintenance nightmare

https://github.com/StephenCleary/CalculatedProperties is best formula engine as of now for MVVM (in my opinion) that notifies about changes of derived/calculated properties and supports any level of nesting, most importantly tree of dependencies can span across multiple objects and can dynamically change at runtime.

  public decimal ItemPrice 
  { 
    get { return Property.Get(0m); }
    set { Property.Set(value); }
  }

  public int Quantity 
  { 
    get { return Property.Get(0); }
    set { Property.Set(value); }
  }

  public decimal TotalPrice
  {
    get { return Property.Calculated(() => ItemPrice * Quantity); }    
  }

This is very similar to Excel formulas but for MVVM. ItemPrice nor Quantity don't know what depends on them and don't care about raising PropertyChanged for dependant TotalPrice. Tree of dependencies can have as many levels as needed.



来源:https://stackoverflow.com/questions/2235890/inotifypropertychanged-and-calculated-property

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!