缺陷以及解决方案
在Avalonia测试时,会发现一个很奇怪的现象:如果将BindingList作为列表控件的ItemSource使用,在添加/删除元素时,尽管TotalValue会被正确更新,但列表没有任何变化。同时,Count属性也没有得到正确通知
查看Avalonia中ItemSourceView的代码后发现,它只通过INotifyCollectionChanged的CollectionChanged事件来刷新列表,而BindingList并未实现和INotifyCollectionChanged接口,这也就是为什么BindingList无法正确通知UI
同时,BindingList也未实现INotifyPropertyChanged,造成Count属性未更新
WinUI 3中,列表未刷新但Count属性能更新,可能是不同UI框架实现的问题
private protected void SetSource(IEnumerable source)
{
...
if (_listening && _source is INotifyCollectionChanged inccNew)
CollectionChangedEventManager.Instance.AddListener(inccNew, this);
}
现在解决方法就很简单了:继承BindingList,实现这两个接口并在添加/移除元素进行通知即可。
完整代码如下:
public class ObservableBindingList<T> : BindingList<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
public event NotifyCollectionChangedEventHandler? CollectionChanged;
public event PropertyChangedEventHandler? PropertyChanged;
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
CollectionChanged?.Invoke(index, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]"));// 通知集合索引器的变化(通过索引器绑定列表第几项时使用)
}
protected override void RemoveItem(int index)
{
var item = this[index];
base.RemoveItem(index);
CollectionChanged?.Invoke(index, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]"));
}
}
使用:
[ObservableProperty]
public partial ObservableBindingList<Item> Items { get; set; } = [];
public int TotalValue => Items.Sum(i => i.Value);
public MainViewModel()
{
// 此处OnPropertyChanged为MVVM工具包中ObservableObject的代码,可替换为PropertyChanged?.Invoke()
Items.ListChanged += (s, e) => OnPropertyChanged(nameof(TotalValue));
}
现在,增删(移动)元素/修改元素内部的值均可正确通知界面