21

这个问题将表明我在实施/使用 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 实现中订阅地址对象上的更改通知,然后将它们作为“地址”的更改传播?

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

4

4 回答 4

3

最简单的方法之一是向 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"))
   }
}
于 2010-08-01T22:22:16.630 回答
1

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

...说使用绑定某人正在订阅更改 Person 对象的通知,

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

于 2009-08-26T18:26:59.260 回答
0

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

对于您的示例,您将在视图中绑定到“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; });
    }
于 2012-08-21T17:37:24.797 回答
0

一个老问题,不过...

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

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

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

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

https://github.com/buunguyen/notify

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

于 2016-04-29T09:24:07.660 回答