使用 ObservableCollection<T>






4.56/5 (22投票s)
在 WPF 中最常用的类之一可以在 System.ComponentModel 命名空间中找到,即 ObservableCollection。
引言
在 WPF 中最常用的类之一可以在 System.ComponentModel
命名空间中找到,即 ObservableCollection<T>
。此类会通知相关方其内部项集合的更改。
特别是关于 WPF,ObservableCollection
类允许您绑定 ItemsControl
派生类,如下所示:<ListBox ItemsSource="{Binding Contacts}"/>
。假设 Listbox 的 DataContext 具有一个名为 Contacts 的属性(必须是集合类型),Listbox 将使用 ListboxItems 为您填充 Listbox。如果该属性的类型为 ObservableCollection
,则 Listbox 会自动将更改传播到 UI。
该属性看起来像这样
private ObservableCollection<ContactViewModel> _Contacts;
public ObservableCollection<ContactViewModel> Contacts
{
get
{
if (_Contacts == null)
{
_Contacts = new ObservableCollection<ContactViewModel>();
}
return _Contacts;
}
}
集合的视图
但是,在幕后,Listbox 并非直接绑定到集合,实际上,它绑定到 CollectionView
对象。根据集合实现的接口,框架选择要创建的 CollectionView
类型。由于我们正在谈论 ObservableCollection
,框架将创建一个 ListCollectionView
。ListCollectionView
提供了过滤、分组和排序*的实用程序。
使用视图的选项
为了访问这些实用程序,您有多种选择。 一种方法是调用一个名为 CollectionViewSource.GetDefaultView
的静态方法,该方法仅采用一个源对象作为参数,并返回一个实现 ICollectionView
接口的对象。这要求您进行强制转换才能获得可以使用的具体类。为了正确转换,您必须知道集合实现的接口。这可能会有点烦人,即使对于 ObservableCollection
,您几乎可以肯定要强制转换为 ListCollectionView
。另一件事是所有绑定将共享相同的视图,因此如果您有多个控件绑定到集合,则无法指定两个不同的排序顺序。 从上面的例子中
ListCollectionView colView = (ListCollectionView)(
CollectionViewSource.GetDefaultView(Contacts));
另一个选择是仅将集合公开为 ListCollectionView。 例如
private ObservableCollection<ContactViewModel> _Contacts;
private ListCollectionView _ContactsView;
public ListCollectionView ContactsView
{
get
{
if (_ContactsView == null)
{
_ContactsView = new ListCollectionView(_Contacts);
}
return _ContactsView;
}
}
这种方法的问题很快就会变得明显。 您的类的用户不再可以访问支持 CollectionView
的强类型集合。 实际上,ListCollectionView
确实提供了一个名为 SourceCollection
的属性,但它的类型为 IEnumerable
,因此您失去了集合的类型以及集合中项目的类型。
我为解决此问题的倒数第二次尝试是简单地公开两个属性。 一个是 ObservableCollection
,另一个是 ListCollectionView
类型。 这使您受益于 CollectionView
,并允许 ViewModels
的用户(例如单元测试或其他 ViewModels)访问强类型的源集合。
这是代码
private ObservableCollection<ContactViewModel> _Contacts;
public ObservableCollection<ContactViewModel> Contacts
{
get
{
if (_Contacts == null)
{
_Contacts = new ObservableCollection<ContactViewModel>();
}
return _Contacts;
}
}
private ListCollectionView _ContactsView;
public ListCollectionView ContactsView
{
get
{
if (_ContactsView == null)
{
_ContactsView = new ListCollectionView(this.Contacts);
}
return _ContactsView;
}
}
此代码有效,并为您的 ViewModels 的使用者提供了很大的灵活性。 但是,这种公开两个属性来表达一个概念的模式一直让我很恼火,最终我花时间创建了一个更好的解决方案。 这个新的解决方案实际上非常简单,只是涉及子类化 ObservableCollection<T>
,并添加一个 ListCollectionView
类型的属性。 请注意,我想不出一个合适的名称,所以我将其称为 ViewableCollection
。 如果有人能想到更好的名字,请在评论中发布,我将重命名它并重新上传源代码。
事不宜迟,这是 ViewableCollection
类
/// <summary>
/// Represents a dynamic data collection that raises notifications
/// when items are added, removed, moved, or replaced. In addition,
/// this collection also has a View property that offers a bindable
/// version of this collection. Note that this collection can only be edited
/// on the UI thread.
/// </summary>
/// <typeparam name="T">The type of items in this collection.</typeparam>
public class ViewableCollection<T> : ObservableCollection<T>
{
private ListCollectionView _View;
/// <summary>
/// A bindable view of this Observable Collection (of T) that supports
/// filtering, sorting, and grouping.
/// </summary>
public ListCollectionView View
{
get
{
if (_View == null)
{
_View = new ListCollectionView(this);
}
return _View;
}
}
}
有了这个类,我们可以将我们的 MainWindowViewModel
类更改如下
private ViewableCollection<ContactViewModel> _Contacts;
public ViewableCollection<ContactViewModel> Contacts
{
get
{
if (_Contacts == null)
{
_Contacts = new ViewableCollection<ContactViewModel>();
}
return _Contacts;
}
}
我们的绑定变为:<ListBox ItemsSource="{Binding Contacts.View}" />
。
此类会延迟实例化视图,因此如果您从未使用该属性,它将不会占用内存空间。 要将 SortDescriptions 添加到此集合:this.Contacts.View.SortDescriptions.Add(new SortDescription("FirstName", ListSortDirection.Descending));
。
查看演示项目,了解如何实现此概念。 请注意,我覆盖 ContactViewModel
类的 ToString
方法是为了简洁起见,绝不应该在实际的 WPF 应用程序中这样做(而是使用 DataTemplates)。
这实际上是我在 Code Project 上的第一篇文章,因此我感谢对未来文章的任何反馈。 请继续关注另一篇文章,该文章关于创建 ViewableCollection
的子类,该子类增加了对集合通用的命令的支持。