繁体   English   中英

从BindingList中删除PropertyChanged-item导致列表重置

[英]Removing PropertyChanged-item from BindingList causes list to Reset

用户定义的类继承自INotifyPropertyChanged。

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

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

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

如何避免复位事件?

(尚未看到此问题的答案,因此决定同时添加-问题和答案)

Google的“ BindingList Child_PropertyChanged”

给出以下代码片段:

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

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

但是因为PropertyChanged.Invoke在执行之前开始广播之前会收集事件的整个列表,所以无论项目是否在列表中,事件都将被调用。

让以下代码片段演示错误:

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一样),但是如果需要,您可以自行解决。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM