【问题标题】:Removing PropertyChanged-item from BindingList causes list to Reset从 BindingList 中删除 PropertyChanged-item 会导致列表重置
【发布时间】:2015-12-15 03:13:10
【问题描述】:

用户定义的类继承自 INotifyPropertyChanged。

在用户定义的类中,某些属性会广播 PropertyChanged 事件。

在该事件对象本身从 BindingList 中删除期间。

事件继续执行,BindingList 获取 ListChangedType.Reset 事件。

可以做些什么来避免重置事件?

【问题讨论】:

    标签: c# inotifypropertychanged bindinglist propertychanged


    【解决方案1】:

    (还没有看到这个问题的答案,所以决定同时添加 - 问题和答案)

    Google 搜索“BindingList Child_PropertyChanged”

    给出以下代码片段:

    http://referencesource.microsoft.com/#System/compmod/system/componentmodel/BindingList.cs,e757be5fba0e6000,references

    表示如果 BindingList 接收到事件,并且给定的项目不在 BindingList 中 - 它将触发列表重置。

    但是因为 PropertyChanged.Invoke 在开始广播之前在执行之前收集整个事件列表 - 无论如何都会调用事件,而不管项目是否在列表中。

    让下面的代码片段来演示错误:

    程序.cs:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.ComponentModel;
    using System.Reflection;
    using System.Security;
    
    namespace Eventing
    {
    
        public class ThisItem : MulticastNotifyPropertyChanged
        {
            void Test()
            {
            }
    
            String _name;
            public String name2
            {
                get {
                    return _name;
                }
    
                set
                {
                    _name = value;
    
                    Console.WriteLine("---------------------------");
                    Console.WriteLine("Invoke test #2");
                    Console.WriteLine("---------------------------");
                    Invoke(this, new PropertyChangedEventArgs("name"));
                }
            }
    
            public String name1
            {
                get {
                    return _name;
                }
    
                set
                {
                    _name = value;
    #if TESTINVOKE
                    Console.WriteLine("---------------------------");
                    Console.WriteLine("Invoke test #1");
                    Console.WriteLine("---------------------------");
                    InvokeFast(this, new PropertyChangedEventArgs("name"));
    #endif
                }
            }
        };
    
        class Program
        {
            static public BindingList<ThisItem> testList;
    
            static void Main(string[] args)
            {
                testList = new BindingList<ThisItem>();
                ThisItem t = new ThisItem();
                testList.ListChanged += testList_ListChanged;
    
                t.PropertyChanged += t_PropertyChanged;
                t.PropertyChanged += t_PropertyChanged2;
                testList.Add(t);
                t.name1 = "testing";
    
                Console.WriteLine("---------------------------");
                t.PropertyChanged -= t_PropertyChanged;
                t.PropertyChanged -= t_PropertyChanged2;
                t.PropertyChanged += t_PropertyChanged;
                testList.Add(t);
                t.PropertyChanged += t_PropertyChanged2;
    
                t.name2 = "testing";
            }
    
            static void testList_ListChanged(object sender, ListChangedEventArgs e)
            {
                Console.WriteLine("3) List changed: " + e.ListChangedType.ToString() + ((e.ListChangedType == ListChangedType.Reset) ? " (*** UPS! ***)": ""));
            }
    
            static void t_PropertyChanged2(object sender, PropertyChangedEventArgs e)
            {
                Console.WriteLine("2) t_PropertyChanged2: " + e.PropertyName);
            }
    
            static void t_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                Console.WriteLine("1) t_PropertyChanged: " + e.PropertyName);
                testList.Remove((ThisItem)sender);
            }
        }
    }
    

    MulticastNotifyPropertyChanged.cs:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace System
    {
        /// <summary>
        /// class which implements INotifyPropertyChanged in such manner that event can be broadcasted in safe manner - 
        /// even if given item is removed from BindingList, event in BindingList (Child_PropertyChanged) won't be 
        /// triggered.
        /// </summary>
        public class MulticastNotifyPropertyChanged : INotifyPropertyChanged
        {
            /// <summary>
            /// List of all registered events. List can change during event broadcasting.
            /// </summary>
            List<PropertyChangedEventHandler> _PropertyChangedHandlers = new List<PropertyChangedEventHandler>();
    
            /// <summary>
            /// Next broadcasted event index.
            /// </summary>
            int iFuncToInvoke;
        #if TESTINVOKE
                PropertyChangedEventHandler _PropertyChangedAllInOne;
        #endif
    
            event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
            {
                add
                {
        #if TESTINVOKE
                        _PropertyChangedAllInOne += value;
        #endif
                    _PropertyChangedHandlers.Add(value);
                }
                remove
                {
        #if TESTINVOKE
                        _PropertyChangedAllInOne -= value;
        #endif
                    int index = _PropertyChangedHandlers.IndexOf(value);
    
                    if (index == -1)
                        return;
    
                    if (iFuncToInvoke >= index)     //Scroll back event iterator if needed.
                        iFuncToInvoke--;
    
        #if TESTINVOKE
                        Console.WriteLine("Unregistering event. Iterator value: " + iFuncToInvoke.ToString());
        #endif
                    _PropertyChangedHandlers.Remove(value);
                }
            }
    
            /// <summary>
            /// Just an accessor, so no cast would be required on client side.
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged
            {
                add
                {
                    ((INotifyPropertyChanged)this).PropertyChanged += value;
                }
                remove
                {
                    ((INotifyPropertyChanged)this).PropertyChanged -= value;
                }
            }
    
            /// <summary>
            /// Same as normal Invoke, except this plays out safe - if item is removed from BindingList during event broadcast -
            /// event won't be fired in removed item direction.
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            public void Invoke(object sender, PropertyChangedEventArgs e)
            {
                for (iFuncToInvoke = 0; iFuncToInvoke < _PropertyChangedHandlers.Count; iFuncToInvoke++)
                {
        #if TESTINVOKE
                        Console.WriteLine("Invoke: " + iFuncToInvoke.ToString());
        #endif
                    _PropertyChangedHandlers[iFuncToInvoke].Invoke(sender, e);
                }
            }
    
        #if TESTINVOKE
                public void InvokeFast( object sender, PropertyChangedEventArgs e )
                {
                    _PropertyChangedAllInOne.Invoke(sender, e);
                }
        #endif
        }
    
    } //namespace System
    

    将导致以下执行流程:

    3) List changed: ItemAdded
    ---------------------------
    Invoke test #1
    ---------------------------
    1) t_PropertyChanged: name
    Unregistering event. Iterator value: 0
    3) List changed: ItemDeleted
    2) t_PropertyChanged2: name
    3) List changed: Reset (*** UPS! ***)
    ---------------------------
    Unregistering event. Iterator value: -1
    Unregistering event. Iterator value: -1
    3) List changed: ItemAdded
    ---------------------------
    Invoke test #2
    ---------------------------
    Invoke: 0
    1) t_PropertyChanged: name
    Unregistering event. Iterator value: 0
    3) List changed: ItemDeleted
    Invoke: 1
    2) t_PropertyChanged2: name
    

    这解决了列表事件 - 但是在事件期间添加到 BindingList 的项目会产生更多问题。这段代码可能我们也可以修复为不向新添加的项目广播事件(就像普通的 BindingList 一样),但如果需要,您可以自己解决。

    【讨论】:

      猜你喜欢
      • 2013-12-17
      • 2016-11-27
      • 1970-01-01
      • 1970-01-01
      • 2021-07-25
      • 2018-06-04
      相关资源
      最近更新 更多