如何让父类知道其子类的变化?

发布于 2024-11-19 22:53:55 字数 2149 浏览 1 评论 0原文

这是示例代码:

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") 放在哪里?当孩子发生变化时,如何让父母知道?

提前致谢

This is an example code:

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

Let's say I now have two instances of my classes, one as myParent, and the other as myChild.
I have two visual elements, that each have a property bound to the IsChnaged property of my instances; ElementA bound to myParent.IsChanged and ElementB bound to myChild.IsChanged.

When myChild.Value differs from its default value, the myChild.IsChanged is set to true and the ElementB is updated accordingly.

What I need is when either of the myParent children (which here is only one) have their IsChanged value set to true, its own (the parent's) IsChanged value be set to true and its corresponding
element (ElementA here) be updated accordingly.

The myParent.IsChanged is only read once (when the binding is set) and it has no sense about its children changing. Where should i put the RaiseChanged("IsChanged") for MyParent? How can I let the parent know when its children have changed?

Thanks in advance

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

背叛残局 2024-11-26 22:53:56

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

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

INotifyPropertyChanged has already provided the mechanism for you: the PropertyChanged event. Just have the parent add a handler to its children's PropertyChanged, and then in that handler call RaiseChanged("IsChanged");

Also, you may want to put the INotifyPropertyChanged implementation in a base class, and have your (what appear to be) ViewModels inherit from that. Not required for this option, of course, but it will make the code a little cleaner.

Update: In the parent object:

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

You don't actually have to add anything to the child class, since it is already raising the correct event when it changes.

堇色安年 2024-11-26 22:53:56

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

我真的很喜欢 codeplex 上的 连续 LINQ 项目 的强大功能和简单性。它具有一些非常丰富的功能,用于设置“反应对象”、“连续值”和“连续集合”。这些允许您将条件定义为 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 来优化此操作。这样,您就不会开始监视更改,直到您知道其他一些代码确实关心更改。

Doing this kind of communication can be tricky, especially if you want to avoid memory leaks due to the event handlers that you hook up. There is also the case of handling items that are added / removed from the collection.

I've really enjoyed the power and simplicity of the Continuous LINQ project on codeplex. It has some very rich features for setting up "Reactive Objects", "Continuous Values", and "Continuous Collections". These let you define your criteria as a Linq expression and then let the CLINQ library keep the underlying values up to date in real time.

In your case, you could set up the parent with a ContinuousFirstOrDefault() linq query that watched for any child where "IsChanged == true". As soon as a child sets the value to true and raises PropertyChanged, the continuous value will detect the change and raise a corresponding PropertyChanged in the parent.

The benefits:

  1. Weak references and weak events are used to prevent the event handlers in the parent from locking the child in memory. It can get very messy to add / remove these handlers from all the children.
  2. You can declare the dependency in the parent without need to make special changes in the child or make the child aware of the parent. Rather, the child just needs to properly implement INotifyPropertyChanged. This puts the "logic" close to the object that cares, rather than spreading event craziness and inter-dependencies all over the code.

Here's what the code might look like:

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

The above code sets up the ContinuousFirstOrDefault in the constructor so that it is always monitoring. However, in some cases you can optimize this by lazily instantiating the ContinuousFirstOrDefault only when the getter for "IsChanged" is called. That way you don't start monitoring for changes until you know that some other piece of code actually cares.

幽梦紫曦~ 2024-11-26 22:53:56

您可以通过将子项存储在 ItemObservableCollection 中来自己简化事情,如 这个答案。这将允许你这样做:

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

You can simplify things for yourself by storing your children in an ItemObservableCollection<T>, as discussed in this answer. That would allow you to do this:

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");
        }
    };
}
过度放纵 2024-11-26 22:53:56

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

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

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

Something I do not see in your code sample provide is an actually reference of parent to child. It is not enough to simply have interface to communicate through, but you must also create the reference. Something like myChild.parent = this; followed by the binding of the event handlers across the channel, in the "parent" property of the child object it would look like:

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

I don't have enough context to perfect this code for you but this should move you in the right direction.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文