Reputation: 51
Suppose, I have objects:
public interface ITest
{
string Data { get; set; }
}
public class Test1 : ITest, INotifyPropertyChanged
{
private string _data;
public string Data
{
get { return _data; }
set
{
if (_data == value) return;
_data = value;
OnPropertyChanged("Data");
}
}
protected void OnPropertyChanged(string propertyName)
{
var h = PropertyChanged;
if (null != h) h(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
and its holder:
private BindingList<ITest> _listTest1;
public BindingList<ITest> ListTest1 { get { return _listTest1 ?? (_listTest1 = new BindingList<ITest>() { RaiseListChangedEvents = true }); }
}
Also, I subscribe to ListChangedEvent
public MainWindow()
{
InitializeComponent();
ListTest1.ListChanged += new ListChangedEventHandler(ListTest1_ListChanged);
}
void ListTest1_ListChanged(object sender, ListChangedEventArgs e)
{
MessageBox.Show("ListChanged1: " + e.ListChangedType);
}
And 2 test handlers: For adding object
private void AddITestHandler(object sender, RoutedEventArgs e)
{
ListTest1.Add(new Test1 { Data = Guid.NewGuid().ToString() });
}
and for changing
private void ChangeITestHandler(object sender, RoutedEventArgs e)
{
if (ListTest1.Count == 0) return;
ListTest1[0].Data = Guid.NewGuid().ToString();
//if (ListTest1[0] is INotifyPropertyChanged)
// MessageBox.Show("really pch");
}
ItemAdded occurs, but ItemChanged not. Inside seeting proprty "Data" I found that no subscribers for my event PropertyChanged:
protected void OnPropertyChanged(string propertyName)
{
var h = PropertyChanged; // h is null! why??
if (null != h) h(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
Digging deeper i took reflector and discover BindingList:
protected override void InsertItem(int index, T item)
{
this.EndNew(this.addNewPos);
base.InsertItem(index, item);
if (this.raiseItemChangedEvents)
{
this.HookPropertyChanged(item);
}
this.FireListChanged(ListChangedType.ItemAdded, index);
}
private void HookPropertyChanged(T item)
{
INotifyPropertyChanged changed = item as INotifyPropertyChanged;
if (changed != null) // Its seems like null reference! really??
{
if (this.propertyChangedEventHandler == null)
{
this.propertyChangedEventHandler = new PropertyChangedEventHandler(this.Child_PropertyChanged);
}
changed.PropertyChanged += this.propertyChangedEventHandler;
}
}
Where am I wrong? Or this is known bug and i need to find some workaround? Thanks!
Upvotes: 4
Views: 3614
Reputation: 4520
The type of elements that you parameterize BindingList<>
with (ITest
in your case) must be inherited from INotifyPropertyChanged. Options:
Upvotes: 1
Reputation: 51
I found in constructor some interesting things:
public BindingList()
{
// ...
this.Initialize();
}
private void Initialize()
{
this.allowNew = this.ItemTypeHasDefaultConstructor;
if (typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(T))) // yes! all you're right
{
this.raiseItemChangedEvents = true;
foreach (T local in base.Items)
{
this.HookPropertyChanged(local);
}
}
}
Quick fix 4 this behavior:
public class BindingListFixed<T> : BindingList<T>
{
[NonSerialized]
private readonly bool _fix;
public BindingListFixed()
{
_fix = !typeof (INotifyPropertyChanged).IsAssignableFrom(typeof (T));
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
if (RaiseListChangedEvents && _fix)
{
var c = item as INotifyPropertyChanged;
if (null!=c)
c.PropertyChanged += FixPropertyChanged;
}
}
protected override void RemoveItem(int index)
{
var item = base[index] as INotifyPropertyChanged;
base.RemoveItem(index);
if (RaiseListChangedEvents && _fix && null!=item)
{
item.PropertyChanged -= FixPropertyChanged;
}
}
void FixPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!RaiseListChangedEvents) return;
if (_itemTypeProperties == null)
{
_itemTypeProperties = TypeDescriptor.GetProperties(typeof(T));
}
var propDesc = _itemTypeProperties.Find(e.PropertyName, true);
OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, IndexOf((T)sender), propDesc));
}
[NonSerialized]
private PropertyDescriptorCollection _itemTypeProperties;
}
Thanks for replies!
Upvotes: 1
Reputation: 4917
BindingList<T>
doesn't check if each particular item implements INotifyPropertyChanged
. Instead, it checks it once for the Generic Type Parameter. So if your BindingList<T>
is declared as follows:
private BindingList<ITest> _listTest1;
Then ITest
should be inherited fromINotifyPropertyChanged
in order to get BindingList
raise ItemChanged
events.
Upvotes: 6
Reputation: 42353
I think we may not have the full picture from your code here, because if I take the ITest
interface and Test1
class verbatim (edit Oops - not exactly - because, as Nikolay says, it's failing for you because you're using ITest as the generic type parameter for the BindingList<T>
which I don't here) from your code and write this test:
[TestClass]
public class UnitTest1
{
int counter = 0;
[TestMethod]
public void TestMethod1()
{
BindingList<Test1> list = new BindingList<Test1>();
list.RaiseListChangedEvents = true;
int evtCount = 0;
list.ListChanged += (object sender, ListChangedEventArgs e) =>
{
Console.WriteLine("Changed, type: {0}", e.ListChangedType);
++evtCount;
};
list.Add(new Test1() { Data = "yo yo" });
Assert.AreEqual(1, evtCount);
list[0].Data = "ya ya";
Assert.AreEqual(2, evtCount);
}
}
The test passes correctly - with evtCount
ending up at 2
, as it should be.
Upvotes: 1