从BindingList中删除PropertyChanged-item会导致列表重置
本文关键字:致列 列表 BindingList 删除 PropertyChanged-item | 更新日期: 2023-09-27 18:13:11
用户自定义类继承自INotifyPropertyChanged.
在用户定义的类中,某些属性广播PropertyChanged事件。
事件期间对象本身从BindingList中移除。
事件继续执行,BindingList获取ListChangedType。重置事件。
如何避免Reset事件?
(没有看到这个问题的答案,所以决定添加- question和answer)
Google for "BindingList Child_PropertyChanged"
给出如下代码片段:
http://referencesource.microsoft.com//compmod/系统/componentmodel/BindingList.cs e757be5fba0e6000、引用
表示如果事件被BindingList接收,并且给定的项目不在BindingList中,它将触发列表重置。
但是因为PropertyChanged。调用在开始广播之前收集整个事件列表-无论事件是否在列表中,事件都将被调用。
让以下代码片段演示错误:
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中的项目产生更多问题。这段代码可能也可以修正为不将事件广播到新添加的项目(像普通的BindingList那样),但如果需要,您可以自己解决。