![](/img/trans.png)
[英]C# Removing bindinglist item object on datagridview causes Exception
[英]Removing PropertyChanged-item from BindingList causes list to Reset
用户定义的类继承自INotifyPropertyChanged。
在用户定义的类中,某些属性广播PropertyChanged事件。
在该事件期间,对象本身会从BindingList中删除。
事件继续执行,并且BindingList获取ListChangedType.Reset事件。
如何避免复位事件?
(尚未看到此问题的答案,因此决定同时添加-问题和答案)
Google的“ BindingList Child_PropertyChanged”
给出以下代码片段:
意味着如果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.