(Не видел ответа на этот вопрос, поэтому решил добавить оба - вопрос и ответ)
Google по запросу "BindingList Child_PropertyChanged"
дает следующий фрагмент кода:
http://referencesource.microsoft.com/#System/compmod/system/componentmodel/BindingList.cs,e757be5fba0e6000,references
Означает, что если событие получено 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), но это то, что вы можете решить самостоятельно, если это необходимо.
person
TarmoPikaro
schedule
18.09.2015