从BindingList中删除PropertyChanged项会导致列表重置

时间:2015-09-18 04:49:44

标签: c# inotifypropertychanged bindinglist propertychanged

用户定义的类继承自INotifyPropertyChanged。

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

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

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

可以采取哪些措施来避免重置事件?

1 个答案:

答案 0 :(得分:0)

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

Google for“BindingList Child_PropertyChanged”

提供以下代码snipet:

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

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

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

让我们按照代码snipet来演示错误:

的Program.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 durign事件的项目中产生更多问题。这段代码可能我们也可以修复为不向新添加的项目广播事件(就像普通的BindingList那样),但如果需要,你可以自己解决这个问题。

相关问题