【问题标题】:WPF INotifyPropertyChanged for linked read-only propertiesWPF INotifyPropertyChanged 用于链接的只读属性
【发布时间】:2011-04-04 10:47:03
【问题描述】:

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

【问题讨论】:

    标签: c# wpf inotifypropertychanged


    【解决方案1】:

    我意识到这是一个老问题,但它是“NotifyPropertyChanged of linked properties”的第一个 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; }
    }
    

    【讨论】:

    • 如果我在 A 类中有 AnyState 属性怎么办。这基本上是从其他两个属性中得到的,每个属性都在一个单独的类中。道具 A 得到 { 返回 B.IsRunning || C.IsRunning;} B 类具有 IsRunning 属性,C 也是如此。
    • @Luishg 如果属性在不同的类中,您将需要不同的解决方案。此属性仅适用于单个类的计算属性。
    【解决方案2】:

    表明 bar 已更改的一种方法是在 foo 设置器中添加对 onPropertyChanged(this, "bar") 的调用。丑得要命,我知道,但你有它。

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

    【讨论】:

    • 我不明白为什么你认为触发 onPropertyChanged(this, "bar") 特别难看?如果 .bar 依赖于 3 个属性,并且您在所有三个 setter 中都进行了调用,我可以看到它......但随后他可以将计算移出 .bar 的属性获取器并进入调用更新的计算方法方法。
    • 这很难看,因为您将属性 B 的正确行为所需的逻辑放在属性 A 的设置器中。可以有任意数量的属性取决于 A 的值。必须修改 A满足使用 A 的其他属性的需求是不明显且不可扩展的。
    • 一个更简洁的解决方案是有某种方式来表明 B 依赖于 A 并且当 A 发生变化时 B 也应该发出值变化的信号。上面的第二个选项,听自己的 PropertyChanged 事件,就是这种风格,但听自己的事件也有点古怪。
    • 呵呵 - 我认为对于丑陋代码的构成,您的门槛比我低...:P 我已经学会了“欣赏”不是事件汤的 UI 逻辑.. .
    • 我认为foo 不应该负责为bar 提高PropertyChanged。这会起作用,但是如果这些属性在另一个类上怎么办?然后,您将不得不以另一种方式实现它。我已经在另一个问题中回答了这个问题,即使该属性在同一类或另一个类中,解决方案也是相同的stackoverflow.com/questions/43653750/…
    【解决方案3】:

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

    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 调用放入 Foo 设置器本质上并没有太大不同,因为您必须为添加的每个新依赖属性更新依赖关系映射。

    但它确实可以随后实现PropertyChangeDependsOnAttribute 并使用反射来扫描类型并构建依赖关系图。这样,您的财产将如下所示:

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

    【讨论】:

    • 你能展示你对PropertyChangeDependsOn系统的实现吗?
    • 唉,我只是断言可以实现它。实际上实现它并不是我需要做的事情。
    • 谢谢。我自己实现了它,我可以轻松地创建一个有效的缓存以避免重新加载反射关系。
    • 可能希望对此进行递归——以便从另一个计算属性等计算的属性得到适当的通知。 -- 有没有一个开源库已经这样做了?
    【解决方案4】:

    你可以简单地调用

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

    【讨论】:

    • 当我需要为一堆属性重复时,这似乎是一个更好的可扩展解决方案。
    • 订阅 PropertyChanged 事件并触发条形图更改通知正是 PRISM 在其 Commanding Quickstart 中所做的。他们的例子更好 - if (propertyName == "Price" || propertyName == "Quantity" || propertyName == "Shipping" ) { this.NotifyPropertyChanged( "Total" ); }
    【解决方案5】:

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

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

    【讨论】:

    • 在我的例子中,用户需要看到它并且程序也需要访问它,但这听起来很有趣。
    【解决方案6】:

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

    -丹

    【讨论】:

      【解决方案7】:

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

      在 ContinousLinq 中,这非常适合集合。

      private SelfUpdatingExpression<int> m_fooExp = new SelfUpdatingExpression<int>(this, ()=> Foo * Foo);
      
      public int Foo
      {
          get
          { 
              return  m_fooExp.Value;   
          }
      }
      

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

      【讨论】:

      • WPF 可以直接绑定自更新表达式吗? -- 如果是这样,为什么不只返回该类型而不是计算的 int?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-17
      • 2014-01-21
      • 1970-01-01
      • 1970-01-01
      • 2011-04-02
      • 1970-01-01
      • 2021-06-25
      相关资源
      最近更新 更多