【问题标题】:When nesting properties that implement INotifyPropertyChanged must the parent object propagate changes?当嵌套实现 INotifyPropertyChanged 的​​属性时,父对象必须传播更改吗?
【发布时间】:2010-11-23 02:41:59
【问题描述】:

这个问题将表明我在实施/使用 INotifyPropertyChanged 时缺乏对预期行为的理解:

问题是 - 为了让绑定按预期工作,当您有一个自身实现 INotifyPropertyChanged 的​​类时,该类具有 INotifyPropertyChanged 类型的嵌套属性,您是否希望在内部订阅这些属性的更改通知,然后传播通知?或者绑定基础架构是否具有使这变得不必要的智能?

例如(注意这段代码并不完整——只是为了说明问题):

   public class Address : INotifyPropertyChanged
    {
       string m_street
       string m_city;

       public string Street
       {
          get { return m_street; }
          set
          {
             m_street = value;
             NotifyPropertyChanged(new PropertyChangedEventArgs("Street"));
          }
       }

       public string City
       {
          get { return m_city; }
          set 
          {
             m_city = value;
             NotifyPropertyChanged(new PropertyChangedEventArgs("City"));
          }
       }

    public class Person : INotifyPropertyChanged
    {
       Address m_address;

       public Address
       {
          get { return m_address = value; }
          set
          {
             m_address = value;
             NotifyPropertyChanged(new PropertyChangedEventArgs("Address"));
          }
       }
    }

因此,在此示例中,我们在 Person 对象中嵌套了 Address 对象。两者都实现了 INotifyPropertyChanged,因此更改其属性将导致向订阅者传输属性更改通知。

但是假设使用绑定某人正在订阅 Person 对象的更改通知,并且正在“侦听”对 Address 属性的更改。如果 Address 属性本身发生更改(分配了不同的 Address 对象),它们将收到通知,但如果嵌套地址对象(城市或街道)包含的数据发生更改,它们将不会收到通知。

这导致了一个问题 - 绑定基础设施是否应该处理这个问题,或者我应该在我的 Person 实现中订阅地址对象上的更改通知,然后将它们作为“地址”的更改传播?

如果您到了这一点,感谢您花时间阅读这个冗长的问题?

【问题讨论】:

  • 我在谷歌搜索后发现了这个问题。在我看来,您似乎必须手动订阅儿童的 PropertyChanged 事件并将其冒泡以在 WPF 绑定中工作。
  • loraderon,我很确定情况并非如此——至少在我的测试中,事实证明是这样。而且,没有其他信息(我发现)可以说明。你有任何链接到你可以提供的任何信息吗?谢谢。菲尔
  • 我也没有任何链接。在我当前的项目中,我不得不冒泡 PropertyChanged 事件以使其工作。我是 WPF 和 MVVM 的新手,所以它可能对我的项目来说很特别。

标签: .net inotifypropertychanged


【解决方案1】:

最简单的方法之一是向 Person 添加一个事件处理程序,它将处理来自 m_address 对象的通知事件:

public class Person : INotifyPropertyChanged
{
   Address m_address;

   public Address
   {
      get { return m_address = value; }
      set
      {
         m_address = value;
         NotifyPropertyChanged(new PropertyChangedEventArgs("Address"));
         m_address.PropertyChanged += new PropertyChangedEventHandler( AddressPropertyChanged );
      }
   }
   void  AddressPropertyChanged( object sender, PropertyChangedEventArgs e )
   {
       NotifyPropertyChanged(new PropertyChangedEventArgs("Address"))
   }
}

【讨论】:

  • tkola,我知道如何为孩子实现属性更改通知。问题是,这是数据绑定正常工作所必需的。从我所看到的来看,答案似乎是否定的——当子对象更改时,您不需要执行更改通知:绑定基础架构已经有效地处理了它(至少对于 WPF)
  • 在将 m_address 设置为地址集中的新值之前,您可能希望取消订阅旧 m_address 的 PropertyChanged 事件。
  • 如果 Address 是 DependencyProperty 怎么办?
【解决方案2】:

你说的时候回答了这个问题

...说使用绑定某人是 订阅更改通知 一个 Person 对象,

有人订阅了 Person 并且无法知道 Address 是否已更改。 所以你必须自己处理这种情况(这很容易实现)。

【讨论】:

  • 真的是这样吗?例如在 WPF 中,我可以这样做 这里,(如果我'是正确的!),绑定基础结构将确保如果 Address 属性更改或 Street/City 更改,则更新城市和街道文本框。
  • 很抱歉,xaml 在评论中的表现不太好。无论如何,我想说的是,它可能是调用者(使用 Person 对象的实体)在 person 对象和任何嵌套属性对象上注册更改通知的要求调用者使用的。我并不是说是这种情况!...这就是我问原始问题的原因,因为我相信两种设计都是可能的(将责任推给用户或实施者)。我试过查看 MS 文档,但没有找到任何确定的东西。干杯!
  • 对不起,假设您使用的是 Winfomrs。我对 WPF 知之甚少,但是,我猜在 WPF 中它也将以完全相同的方式工作。 WPF 确实具有冒泡事件的概念,您可能必须利用这一事实。看这篇文章,它应该解释你创建自定义路由事件msdn.microsoft.com/en-us/library/ms742806.aspx
  • 感谢 PK,我使用了路由事件和依赖属性,但它们并不真正适用于我正在使用的东西。我正在对 WPF 应用程序使用 MVVM 方法(如果您使用一些 WPF,强烈建议您研究它 - 它是 WPF MVP 的一种特定变体,但由于 WPF 的数据驱动 UI 而非常强大)。使用 MVVM 方法,更改通知有两种选择 - WPF 依赖属性或 INotifyPropertyChanged。通常最好使用 Inotify,因为它适用于任何类(没有继承要求)。谢谢!
【解决方案3】:

如果您想将子对象视为直接作为其父对象的一部分,您需要自己进行冒泡。

对于您的示例,您将在视图中绑定到“Address.Street”,因此您需要冒泡一个包含该字符串的 notifypropertychanged。

我写了一个简单的助手来做到这一点。您只需在父视图模型构造函数中调用 BubblePropertyChanged(x => x.BestFriend) 。注意假设您的父级中有一个名为 NotifyPropertyChanged 的​​方法,但您可以对其进行调整以适应。

        /// <summary>
    /// Bubbles up property changed events from a child viewmodel that implements {INotifyPropertyChanged} to the parent keeping
    /// the naming hierarchy in place.
    /// This is useful for nested view models. 
    /// </summary>
    /// <param name="property">Child property that is a viewmodel implementing INotifyPropertyChanged.</param>
    /// <returns></returns>
    public IDisposable BubblePropertyChanged(Expression<Func<INotifyPropertyChanged>> property)
    {
        // This step is relatively expensive but only called once during setup.
        MemberExpression body = (MemberExpression)property.Body;
        var prefix = body.Member.Name + ".";

        INotifyPropertyChanged child = property.Compile().Invoke();

        PropertyChangedEventHandler handler = (sender, e) =>
        {
            this.NotifyPropertyChanged(prefix + e.PropertyName);
        };

        child.PropertyChanged += handler;

        return Disposable.Create(() => { child.PropertyChanged -= handler; });
    }

【讨论】:

    【解决方案4】:

    一个老问题,不过......

    我最初的方法是将子属性更改为父属性。这有一个优势,消费父级的事件很容易。只需要订阅父节点。

    public class NotifyChangedBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        readonly Dictionary<string, AttachedNotifyHandler> attachedHandlers = new Dictionary<string, AttachedNotifyHandler>();
    
        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        protected void AttachPropertyChanged(INotifyPropertyChanged notifyPropertyChanged,
            [CallerMemberName] string propertyName = null)
        {
            if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
            // ReSharper disable once ExplicitCallerInfoArgument
            DetachCurrentPropertyChanged(propertyName);
            if (notifyPropertyChanged != null)
            {
                attachedHandlers.Add(propertyName, new AttachedNotifyHandler(propertyName, this, notifyPropertyChanged));
            }
        }
    
        protected void DetachCurrentPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
            AttachedNotifyHandler handler;
            if (attachedHandlers.TryGetValue(propertyName, out handler))
            {
                handler.Dispose();
                attachedHandlers.Remove(propertyName);
            }
        }
    
        sealed class AttachedNotifyHandler : IDisposable
        {
            readonly string propertyName;
            readonly NotifyChangedBase currentObject;
            readonly INotifyPropertyChanged attachedObject;
    
            public AttachedNotifyHandler(
                [NotNull] string propertyName,
                [NotNull] NotifyChangedBase currentObject,
                [NotNull] INotifyPropertyChanged attachedObject)
            {
                if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
                if (currentObject == null) throw new ArgumentNullException(nameof(currentObject));
                if (attachedObject == null) throw new ArgumentNullException(nameof(attachedObject));
                this.propertyName = propertyName;
                this.currentObject = currentObject;
                this.attachedObject = attachedObject;
    
                attachedObject.PropertyChanged += TrackedObjectOnPropertyChanged;
            }
    
            public void Dispose()
            {
                attachedObject.PropertyChanged -= TrackedObjectOnPropertyChanged;
            }
    
            void TrackedObjectOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
            {
                currentObject.OnPropertyChanged(propertyName);
            }
        }
    }
    

    用法很简单:

    public class Foo : NotifyChangedBase
    {
        Bar bar;
    
        public Bar Bar
        {
            get { return bar; }
            set
            {
                if (Equals(value, bar)) return;
                bar = value;
                AttachPropertyChanged(bar);
                OnPropertyChanged();
            }
        }
    }
    
    public class Bar : NotifyChangedBase
    {
        string prop;
    
        public string Prop
        {
            get { return prop; }
            set
            {
                if (value == prop) return;
                prop = value;
                OnPropertyChanged();
            }
        }
    }
    

    但是,这种方法不是很灵活,并且无法对其进行控制,至少无需额外的复杂工程。如果订阅系统具有遍历嵌套数据结构的灵活性,那么它的适用性仅限于一级子级。

    虽然这些警告可能是可以接受的,但根据使用情况,我已经放弃了这种方法,因为它永远无法确定最终将如何使用数据结构。目前更喜欢这样的解决方案:

    https://github.com/buunguyen/notify

    这样即使是复杂的数据结构也是简单且可预测的,订阅者可以控制如何订阅和如何做出反应,它与绑定引擎的功能配合得很好。

    【讨论】:

      猜你喜欢
      • 2012-01-26
      • 2012-08-05
      • 1970-01-01
      • 2020-03-09
      • 1970-01-01
      • 2012-04-25
      • 1970-01-01
      • 1970-01-01
      • 2019-06-06
      相关资源
      最近更新 更多