【问题标题】:INotifyPropertyChanged and calculated propertyINotifyPropertyChanged 和计算属性
【发布时间】:2011-01-15 04:57:27
【问题描述】:

假设我有一个简单的 Order 类,它有一个 TotalPrice 计算属性,可以绑定到 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; }    
  }
}

在影响 TotalPrice 计算的属性中调用 RaisePropertyChanged("TotalPrice") 是否是一种好习惯?刷新 TotalPrice 属性的最佳方法是什么? 另一个版本当然是改变这样的属性

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

    }
}

并调用 TotalPrice = -1 而不是 this.RaisePropertyChanged("TotalPrice");在其他属性中。请提出更好的解决方案

非常感谢

【问题讨论】:

  • 我认为ItemPriceQuantity 不应该负责为TotalPrice 提高PropertyChanged。这会起作用,但如果ItemPriceQuantity 在另一个班级怎么办 - 那么你将无法做到这一点,而必须以另一种方式做到这一点。我已经在另一个问题中回答了这个问题,即使属性在同一类或其他类中,答案也是相同的:stackoverflow.com/questions/43653750/…

标签: c# wpf inotifypropertychanged


【解决方案1】:

另一个解决方案是 Robert Rossney 在这个问题中提出的解决方案:

WPF INotifyPropertyChanged for linked read-only properties

你可以创建一个属性依赖映射(使用他的代码示例):

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

您甚至可以附加一个属性来将依赖属性绑定到它所依赖的属性。比如:

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

我还没有实现属性的细节。我最好现在就着手处理。

【讨论】:

  • 我一直在寻找一个可以做到这一点的开源库——我可以自己编写它,但必须有人已经完成了。
【解决方案2】:

您可以检查一下您是否也应该从可能更改值的任何其他成员那里引发此事件,但只有在您确实更改该值时才这样做。

您可以将其封装在一个方法中:

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

然后你需要从你的其他变异成员那里调用它:

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

【讨论】:

    【解决方案3】:

    在影响 TotalPrice 计算的属性中调用 RaisePropertyChanged("TotalPrice") 是否是一种好习惯?

    不,它不是,它不能扩展并且(属性应该知道依赖它的一切)是维护的噩梦

    https://github.com/StephenCleary/CalculatedProperties 是目前 MVVM 的最佳公式引擎(在我看来),它通知派生/计算属性的更改并支持任何级别的嵌套,最重要的是依赖树可以跨越多个对象并且可以动态更改在运行时。

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

    这与 Excel 公式非常相似,但适用于 MVVM。 ItemPrice 和 Quantity 不知道什么依赖于它们,也不关心为依赖 TotalPrice 提高 PropertyChanged。依赖关系树可以根据需要有多个级别。

    【讨论】:

    • 我认为在 .net 4.5+ 上调用 nameof(Total) 的最佳方式
    • 我没有提到硬编码字符串与 nameof() - 反模式是原始示例中的触发器属性(数量和 ItemPrice)知道它们在派生的上游属性中的用法。 p.s. CalculatedProperties 使用 [CallerMemberName] 属性来避免硬编码属性名称
    • 如果我们使用 RaisePropertyChanged(nameof(TotalPrice)) 而不是如果属性名称将被更改,代码将无法编译......所以这应该足以使其按原样使用
    • 我们谈论不同的事情
    【解决方案4】:

    如果您使用NotifyPropertyWeaver,您可以获得此代码

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

    它会被编译成这个。

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

    【讨论】:

      猜你喜欢
      • 2013-01-14
      • 2011-03-21
      • 2014-01-21
      • 1970-01-01
      • 2017-08-26
      • 1970-01-01
      • 1970-01-01
      • 2010-12-12
      • 2017-12-18
      相关资源
      最近更新 更多