65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 ObservableCollection<T>

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (22投票s)

2009年12月17日

CPOL

3分钟阅读

viewsIcon

140778

downloadIcon

3271

在 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,框架将创建一个 ListCollectionViewListCollectionView 提供了过滤、分组和排序*的实用程序。

使用视图的选项

为了访问这些实用程序,您有多种选择。 一种方法是调用一个名为 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&lt;ContactViewModel&gt; _Contacts;
        public ViewableCollection&lt;ContactViewModel&gt; Contacts
        {
            get
            {
                if (_Contacts == null)
                {
                    _Contacts = new ViewableCollection&lt;ContactViewModel&gt;();
                }
                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 的子类,该子类增加了对集合通用的命令的支持。

© . All rights reserved.