如何让父母阶层了解其子女的变化?

时间:2011-07-11 12:20:40

标签: c# wpf binding parent-child inotifypropertychanged

这是一个示例代码:

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并且它对应 element(此处为ElementA)会相应更新。

myParent.IsChanged只读一次(当设置绑定时)并且它的子节点没有变化。我应该把MyParent的RaiseChanged(“IsChanged”)放在哪里?如果孩子的孩子有所改变,我怎么能让他们知道呢?

提前致谢

5 个答案:

答案 0 :(得分:13)

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

此外,您可能希望将INotifyPropertyChanged实现放在基类中,并使您的(似乎是)ViewModels继承自该实现。当然,这个选项不是必需的,但它会使代码更清晰。

更新:在父对象中:

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

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

答案 1 :(得分:4)

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

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

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

好处:

  1. 弱引用和弱事件用于防止父级中的事件处理程序将子级锁定在内存中。从所有孩子那里添加/删除这些处理程序会非常麻烦。
  2. 您可以在父级中声明依赖项,而无需在子级中进行特殊更改或使子级知道父级。相反,孩子只需要正确实现INotifyPropertyChanged。这使得“逻辑”接近关注的对象,而不是在整个代码中传播事件疯狂和相互依赖。
  3. 以下是代码的外观:

    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来优化它。这样,在您知道其他一些代码真正关心之前,您不会开始监视更改。

答案 2 :(得分:0)

this answer中所述,您可以将孩子存放在ItemObservableCollection<T>中,从而简化自己的工作。那将允许你这样做:

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

答案 3 :(得分:0)

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

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

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

答案 4 :(得分:-2)

我对C ++代码不太熟悉,但这听起来很像“观察者模式”(Wikipediaanother good page for OO design。简单来说,任何实例都应该在事件发生变化时引发事件,以及任何其他持有对它的引用的实例都可以捕获此事件并做出适当的反应。