【问题标题】:How to let a parent class know about a change in its children?如何让父类知道其子类的变化?
【发布时间】:2011-07-11 12:20:40
【问题描述】:

这是一个示例代码:

public class MyParent : INotifyPropertyChanged
{
    List<MyChild> MyChildren;

    public bool IsChanged
    {
        get
        {
            foreach (var child in MyChildren)
            {
                if (child.IsChanged) return true;
            }
            return false;
        }
    }


    public event PropertyChangedEventHandler PropertyChanged;
    private void RaiseChanged(string propName)
    {
        if (PropertyChanged != null)
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
    }
}

public class MyChild : INotifyPropertyChanged
{
    private int _Value;
    public int Value
    {
        get
        {
            return _Value;
        }
        set
        {
            if (_Value == value)
                return;
            _Value = value;
            RaiseChanged("Value");
            RaiseChanged("IsChanged");
        }
    }
    private int _DefaultValue;
    public int DefaultValue
    {
        get
        {
            return _DefaultValue;
        }
        set
        {
            if (_DefaultValue == value)
                return;
            _DefaultValue = value;
            RaiseChanged("DefaultValue");
            RaiseChanged("IsChanged");
        }
    }

    public bool IsChanged
    {
        get
        {
            return (Value != DefaultValue);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void RaiseChanged(string propName)
    {
        if (PropertyChanged != null)
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
    }
}

假设我现在有两个类实例,一个是 myParent,另一个是 myChild。 我有两个视觉元素,每个元素都有一个绑定到我的实例的 IsChnaged 属性的属性; ElementA 绑定到 myParent.IsChanged,ElementB 绑定到 myChild.IsChanged。

当 myChild.Value 与其默认值不同时,myChild.IsChanged 设置为 true 并且 ElementB 会相应更新。

我需要的是当任何一个 myParent 孩子(这里只有一个)将其 IsChanged 值设置为 true,将其自己(父母的)IsChanged 值设置为 true 并且其对应的 元素(此处为 ElementA)进行相应更新。

myParent.IsChanged 只被读取一次(当绑定被设置时)并且它对它的孩子改变没有任何意义。我应该把 MyParent 的 RaiseChanged("IsChanged") 放在哪里?我如何让父母知道它的孩子何时发生了变化?

提前致谢

【问题讨论】:

  • 你可以让你的子构造函数将父对象作为参数,在创建这些子对象时你可以在构造函数中给父对象。
  • 这是我想避免的事情,我宁愿不在构造函数中甚至作为属性将父级介绍给子级。有没有其他办法?

标签: c# wpf binding parent-child inotifypropertychanged


【解决方案1】:

INotifyPropertyChanged 已经为您提供了机制:PropertyChanged 事件。只需让父级将处理程序添加到其子级的PropertyChanged,然后在该处理程序中调用RaiseChanged("IsChanged");

此外,您可能希望将INotifyPropertyChanged 实现放在基类中,并让您的(看起来是的)ViewModel 继承自该基类。当然,此选项不是必需的,但它会使代码更简洁。

更新:在父对象中:

// This list tracks the handlers, so you can
// remove them if you're no longer interested in receiving notifications.
//  It can be ommitted if you prefer.
List<EventHandler<PropertyChangedEventArgs>> changedHandlers =
    new List<EventHandler<PropertyChangedEventArgs>>();

// Call this method to add children to the parent
public void AddChild(MyChild newChild)
{
    // Omitted: error checking, and ensuring newChild isn't already in the list
    this.MyChildren.Add(newChild);
    EventHandler<PropertyChangedEventArgs> eh =
        new EventHandler<PropertyChangedEventArgs>(ChildChanged);
    newChild.PropertyChanged += eh;
    this.changedHandlers.Add(eh);
}

public void ChildChanged(object sender, PropertyChangedEventArgs e)
{
    MyChild child = sender as MyChild;
    if (this.MyChildren.Contains(child))
    {
        RaiseChanged("IsChanged");
    }
}

您实际上不必向子类添加任何内容,因为它在更改时已经引发了正确的事件。

【讨论】:

  • 你能帮我举个例子吗?
  • 我有这个错误:“无法将类型'System.EventHandler'隐式转换为'System.ComponentModel.PropertyChangedEventHandler'”
  • @iXed 很抱歉;没有机会编译和测试。在这种情况下,您可以将处理程序声明为 PropertyChangeEventHandler
  • 感谢您的回答,它工作得很好,但是有一个问题,我想使用序列化来保存我的对象,但是当我打开它们时,打开的对象不再具有 PropertyChanged 处理程序设置给他们。有什么解决办法吗?顺便说一句,我可以编辑您的答案,以免其他人感到困惑吗?
【解决方案2】:

进行这种通信可能会很棘手,特别是如果您想避免由于连接的事件处理程序而导致内存泄漏。还有处理从集合中添加/删除的项目的情况。

我非常喜欢 codeplex 上 Continuous LINQ project 的强大功能和简单性。它有一些非常丰富的功能,用于设置“反应对象”、“连续值”和“连续集合”。这些让您可以将您的条件定义为 Linq 表达式,然后让 CLINQ 库实时更新基础值。

在您的情况下,您可以使用 ContinuousFirstOrDefault() linq 查询设置父级,以监视“IsChanged == true”的任何子级。一旦子级将值设置为 true 并引发 PropertyChanged,连续值将检测到更改并在父级中引发相应的 PropertyChanged。

好处:

  1. 弱引用和弱事件用于防止父级中的事件处理程序将子级锁定在内存中。从所有孩子中添加/删除这些处理程序可能会变得非常混乱。
  2. 您可以在父项中声明依赖项,而无需在子项中进行特殊更改或让子项了解父项。相反,孩子只需要正确实现 INotifyPropertyChanged。这使“逻辑”接近于关心的对象,而不是在整个代码中散布事件的疯狂和相互依赖。

代码如下所示:

public class MyParent : INotifyPropertyChanged
{

    private ObservableCollection<MyChild> _MyChildren;
    private ContinuousValue<MyChild> _ContinuousIsChanged = null;

    public MyParent()
    {
        _MyChildren = new ObservableCollection<MyChild>();

        // Creat the ContinuousFirstOrDefault to watch the MyChildren collection.
        // This will monitor for newly added instances, 
        // as well as changes to the "IsChanged" property on 
        // instances already in the collection.
        _ContinuousIsChanged = MyChildren.ContinuousFirstOrDefault(child => child.IsChanged);
        _ContinuousIsChanged.PropertyChanged += (s, e) => RaiseChanged("IsChanged");
    }

    public ObservableCollection<MyChild> MyChildren
    {
        get { return _MyChildren; }
    }

    public bool IsChanged
    {
        get
        {
            // If there is at least one child that matches the 
            // above expression, then something has changed.
            if (_ContinuousIsChanged.Value != null)
                return true;

            return false;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void RaiseChanged(string propName)
    {
        if (PropertyChanged != null)
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
    }
}

public class MyChild : INotifyPropertyChanged
{
    private int _Value;
    public int Value
    {
        get
        {
            return _Value;
        }
        set
        {
            if (_Value == value)
                return;
            _Value = value;
            RaiseChanged("Value");
            RaiseChanged("IsChanged");
        }
    }
    private int _DefaultValue;
    public int DefaultValue
    {
        get
        {
            return _DefaultValue;
        }
        set
        {
            if (_DefaultValue == value)
                return;
            _DefaultValue = value;
            RaiseChanged("DefaultValue");
            RaiseChanged("IsChanged");
        }
    }

    public bool IsChanged
    {
        get
        {
            return (Value != DefaultValue);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void RaiseChanged(string propName)
    {
        if (PropertyChanged != null)
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
    }
}

上面的代码在构造函数中设置了ContinuousFirstOrDefault,使其始终处于监控状态。但是,在某些情况下,您可以通过仅在调用“IsChanged”的 getter 时延迟实例化 ContinuousFirstOrDefault 来优化这一点。这样一来,在您知道其他代码段真正关心之前,您不会开始监视更改。

【讨论】:

    【解决方案3】:

    您可以通过将孩子存储在ItemObservableCollection&lt;T&gt; 中来为自己简化事情,如this answer 中所述。这将允许您这样做:

    private ItemObservableCollection<MyChild> children;
    
    public MyParent()
    {
        this.children = new ItemObservableCollection<MyChild>();
        this.children.ItemPropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
        {
            if (string.Equals("IsChanged", e.PropertyName, StringComparison.Ordinal))
            {
                this.RaisePropertyChanged("IsChanged");
            }
        };
    }
    

    【讨论】:

    • ItemObservableCollection?还是 ObservableCollection?我试试看,谢谢
    • 不错的解决方案,但仍然没有那么简单
    【解决方案4】:

    我在您提供的代码示例中没有看到的东西实际上是父母对孩子的引用。仅仅通过接口进行通信是不够的,还必须创建引用。像myChild.parent = this; 这样的东西,然后是跨通道的事件处理程序的绑定,在子对象的“父”属性中,它看起来像:

    public INotifyPropertyChanged parent
    {
       get{return _parent;}
       set
       {
          _parent = value;
          this.PropertyChanged += _parent.RaiseChanged();
       }
    }
    

    我没有足够的上下文来为你完善这段代码,但这应该会让你朝着正确的方向前进。

    【讨论】:

    • 这是我想避免的事情,我宁愿不在构造函数中或什至作为属性将父级介绍给子级。但我有点相信我是不对的。
    猜你喜欢
    • 1970-01-01
    • 2017-01-26
    • 2011-03-31
    • 1970-01-01
    • 2020-01-26
    • 2020-08-14
    • 1970-01-01
    • 1970-01-01
    • 2019-05-05
    相关资源
    最近更新 更多