BindingList <t> INotifyPropertyChanged意外行为</t>

时间:2013-06-11 08:09:49

标签: c# inotifypropertychanged bindinglist

假设我有对象:

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;
}

及其持有人:

    private BindingList<ITest> _listTest1;
    public BindingList<ITest> ListTest1 { get { return _listTest1 ?? (_listTest1 = new BindingList<ITest>() { RaiseListChangedEvents = true }); }
    }

另外,我订阅了ListChangedEvent

    public MainWindow()
    {
        InitializeComponent();            
        ListTest1.ListChanged += new ListChangedEventHandler(ListTest1_ListChanged);
    }
    void ListTest1_ListChanged(object sender, ListChangedEventArgs e)
    {
        MessageBox.Show("ListChanged1: " + e.ListChangedType);
    }

2个测试处理程序: 用于添加对象

    private void AddITestHandler(object sender, RoutedEventArgs e)
    {
        ListTest1.Add(new Test1 { Data = Guid.NewGuid().ToString() });
    }

和更改

    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,但没有ItemChanged。在内部预订“数据”中,我发现我的事件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;

深入挖掘我采用反射器并发现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;
        }
    }

我哪里错了?或者这是已知的bug,我需要找到一些解决方法? 谢谢!

4 个答案:

答案 0 :(得分:5)

BindingList<T>不会检查每个特定项目是否实现INotifyPropertyChanged。相反,它会检查一次通用类型参数。因此,如果您的BindingList<T>声明如下:

private BindingList<ITest> _listTest1;

然后,ITest应继承INotifyPropertyChanged,以便BindingList举起ItemChanged个事件。

答案 1 :(得分:1)

我想我们可能没有从您的代码中获得完整的图片,因为如果我采用ITest接口和Test1类逐字(编辑哎呀 - 不完全 - 因为,正如Nikolay所说,你的代码中使用ITest作为BindingList<T>的通用类型参数(我不在这里)并且写下这个测试,这对你来说是失败的:

[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);

  }
}

测试正确传递 - evtCount最终为2,应该是这样。

答案 2 :(得分:1)

使用(BindingList<>在您的情况下)参数化ITest的元素类型必须从INotifyPropertyChanged继承。 选项:

  1. 更改继承树ITest:INotifyPropertyChanged
  2. 将具体类传递给通用BindingList

答案 3 :(得分:0)

我在构造函数中找到了一些有趣的东西:

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);
        }
    }
}

快速修复4此行为:

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;
}

感谢您的回复!

相关问题