C是集合

ItemsControl: 'C' is for Collection

(该系列的第二章是关于ItemsControl的一些有趣的东西,但是跟了解ItemsControl不是很相关,故此略过)

如果没有一个项的集合,那么ItemsControl将会一无所有。在这篇文章里,我们将会调查ItemsControl的Items属性,关注以下话题:

·        The Items Collection

·        Item Collection Modes

·        Observable Collections

·        CollectionView: “The Great Equalizer”

·        Performance Considerations

·        Items and the Element Trees

 

ItemsCollection

Items属性提供了对一个集合中对象的访问方法。这些对象构成了ItemsControl的逻辑内容。该属性的类型是ItemsCollection。ItemsCollection中的对象是Object类型。所以表面上看来,任何CLR对象都可以被加入到该集合中。

在更详细的讨论ItemsCollection之前,还要关注一下Items属性。

1)      该属性是一个只读CLR属性。

这意味着该集合只能被控件本身实例化。实际上,ItemsCollection类甚至没有一个共有的构造函数。

2)      Items属性并不是一个依赖属性。

这意味着你不能在Items属性上应用绑定。然而,你绝对可以将一个集合的对象绑定到ItemsControl上,这点我们稍后再讲。现在让我们先看看非绑定情景下对ItemsControl的使用。

1.     ItemsCollection模式:直接模式和ItemsSource

虽然Items属性是只读的,但是,它所提供的集合不是只读的。实际上,我们已经看到,项可以被直接加入到ItemsControl中:

<ListBox>

 <sys:String>Item 1</sys:String>

 <sys:String>Item 2</sys:String>

 <sys:String>Item 3</sys:String>

</ListBox>

 

上面就是对ItemsCollection使用“直接模式”。直接模式非常简单,在直接模式下,ItemsCollection类跟其它.NET集合类一样。你可以访问集合的任何成员: Add(), Insert(), Remove(), RemoveAt(), IndexOf(),Items[index]。

ItemsControl另一个模式是ItemsSource模式。在此模式下,ItemsCollection中的项对应到另外一个对象集合。这个对象集合通过ItemsControl的另外一个属性指定,这个属性是ItemsSource。

下面的标记显示了如何使用ItemsSource

<ListView ItemsSource="{BindingPath=Characters}">

 <ListView.View>

   <GridView>

     <GridViewColumn Width="100"

       DisplayMemberBinding="{Binding Last}"

       Header="Last Name" />

     <GridViewColumn Width="100"

       DisplayMemberBinding="{Binding First}"

       Header="First Name" />

     <GridViewColumn Width="60"

       DisplayMemberBinding="{Binding Gender}"

       Header="Gender" />

   </GridView>

 </ListView.View>

</ListView>

 

ItemsSource属性是一个类型为IEnumerable的依赖属性。这告诉我们两件事情:

1)      源集合可以是任何enumerable集合。

2)      可以在ItemsSource属性上应用数据绑定。

因为如此,所以可以使用该属性将ItemsControl绑定到一个集合。

备注:虽然你经常看到使用数据绑定来设置ItemsSource,但是没有理由不可以直接设置,如下所示:

<ListBox ItemsSource="{StaticResourceCharacters}" />

 

2.     两种模式相互排斥

需要注意的是直接模式和ItemsSource模式相互排斥。一个ItemsCollection可以应用直接模式或者ItemsSource模式下,但是不能同时应用两种模式。

一旦Items属性被显式加入了对象,那么ItemsCollection就被设置为直接模式。此后如果设置ItemsSource属性将会引发异常。

同样,一旦ItemsSource属性被设置了,ItemsCollection就被设置为ItemsSource模式。此后如果项直接修改Items集合,将会引发异常。

在运行时更改模式有两种方法。

1)      在直接模式下,设置ItemsSource前通过使用Clear方法清除Items集合。

2)      在修改Items属性前,将ItemsSource属性设置为null。

Observable Collections 支持动态更新

在直接模式下,在运行时对ItemsCollection的修改会直接引起UI的更新。

但是在ItemsSource模式下的动态集合会不会引起UI更新呢?ItemsCollection如何知道源集合的变化呢?

答案是ItemsCollection不能知道源集合的变化。除非这些集合提供了变化通知。提供变化通知的方法是这些集合需要实现INotifyCollectionChanged接口。所有实现该接口的集合都被称为可观察集合。

observable collection的任何变化都被直接反映到ItemsControl的ItemsCollection中,并最终引发UI变化。

INotifyCollectionChanged接口并不复杂,但是如果开发人员每次在绑定所用集合时都需要实现该接口,会非常不方便。幸运的是,.NET框架提供了一个叫做ObservableCollection<T>的泛型模板。通过创建此类型的实例,你自动获得集合变化通知的能力。

典型的,你可以看到一个类可以从ObservableCollection<T>类型派生,如下:

public class StringCollection : ObservableCollection<string>

   {

   }

 

任何StringCollection的实例都是可观察的,而且能够ItemsSource属性联合起来工作。

那么如果集合不是可观察的呢?还可以使用在ItemsSource属性上吗?

当然,前面讲到,任何集合都可以赋给ItemsSource属性。唯一的不同是,如果源集合更新了,ItemsCollection将不会自动更新。当ItemsSource属性被赋值的时候,源集合将被遍历,所有的项将会被加入到ItemsCollection中。之后,如果你希望ItemsCollection被更新,那么你需要显式调用ItemsCollection.Refresh方法。

 

3.     CollectionView:“伟大的平衡者”

如果你经常使用ItemsControl,那么你知道排序,分组和过滤这样功能是通过CollectionView支持的。这个类还支持“当前项”。CollectionView维护了一个当前项的指针。该指针可以通过CollectionView的方法来访问及移动。

每个WPF中的enumerable collection都有一个默认视图,同时还可能有其他多个视图,每个视图都有自己的排序分组和过滤参数。建立集合视图的通用方法是通过CollectionViewSource类。如下所示:

<CollectionViewSource x:Key="characterView"

   Source="{StaticResource Characters}">

 <CollectionViewSource.SortDescriptions>

   <componentModel:SortDescription PropertyName="First" />

 </CollectionViewSource.SortDescriptions>

 <CollectionViewSource.GroupDescriptions>

   <dat:PropertyGroupDescription PropertyName="Last" />

 </CollectionViewSource.GroupDescriptions>

</CollectionViewSource>

 

这个CollectionViewSource对象可以作为ItemsControl的ItemsSource属性被指定。此后,其所关联的视图将会被ItemsControl使用。

“但是爸爸,我不想使用CollectionView”

“我没有问你想要什么,只要你在我的屋檐下,你就得使用CollectionView!”

在WPF中,有时候并不关心你想要什么,而是关心框架需要什么。CollectionView就是个经典的例子。框架需要用一种统一的方式来对待所有类型的集合,使得这些集合可以绑定到ItemsControl。

不幸的是,所有的集合并不是生来就平等。例如,IList支持项索引。而IEnumerable需要遍历很多项来得到指定索引的项。此外,有些集合类实现了INotifyCollectionChanged,而Collection<T>没有实现这样的功能。

框架架构师想要对尽可能多的集合类型支持绑定。但是想象如果我们需要对不同类型的集合的不同之处负责,那么ItemsControl中的代码将会有多么难看,如果是可观察的,那么……,否则如果是……,那么……等等。

为了处理这种挑战,WPF引入了CollectionView类作为不同集合的平衡器。是CollectionView类在内部观察集合所支持的接口,并决定如何用最好的方法处理集合。并通过视图公开一系列定义良好的属性,方法和事件。本质上,它允许ItemsControl用同一种方式对待所有集合。

所以无论你关心不关心当前项,排序,分组,过滤,变化通知等等。ItemsControl始终使用CollectionView在内部维护一个项集合。实际上,ItemCollection就是一个项集合。

虽然是真的,但是最后一句话有点误导。因为ItemColleciton只是一个内部CollectionView的包装器。内部的CollectionView的类型由源集合的类型和ItemCollection的模式决定。在直接模式,是InnerItemCollectionView(一个内部类)。在ItemsSource模式下,对于IEnumerable源是CollectionView,对于IList源是ListCollectionView;对于IBindingList或者IBindingListView是BindingListCollectionView。

4.     谁关心你是observable的?

CollectionView关心,我们知道,集合变化引发UI及时更新。这是因为CollectionView类在监听源集合的事件。

绑定集合的性能考虑

记住CollectionView是一个伟大的集合平衡者。对于绑定到一个集合,我们应该考虑一下它的性能问题。我们已经知道,不是所有的集合都支持同样的功能。CollectionView经常需要为源集合执行额外的工作来支持它的通用接口。

CollectionView提供的一个主要功能是为enumerable集合支持索引。也就是说CollectionView需要使用整数下标访问集合。这表示源集合需要支持this[int index]和Count,同时也要支持Contains方法和IndexOf方法。

如果源集合已经支持索引,你会得到更好的性能,这表示最好使用支持IList接口的集合。注意到ObservableCollection<T>实现了IList,所以这是一个不错的选择。

如果源集合不支持索引访问(ICollection,IEnumerable),CollectionView需要做很多的工作填补这些空白。为了支持Count属性,CollectionView可能需要遍历整个集合。其他方法,例如Contains,IndexOf等也是很昂贵的操作。因为这些支持这些操作的算法的性能取决于集合的大小。

所以为了获取高性能,需要源集合尽可能支持IList。

5.     Items和元素树

ItemsCollection中的项构成了ItemsControl的逻辑孩子。它们是逻辑树的成员,对于HeaderedItemsControl,每个项关联的标头也是ItemsControl的逻辑孩子。

如果ItemsCollection中的项恰好是visual。那么这些项也是可视树的孩子。如果他们不是visual,这些项将会被模板展开后的visual所呈现。因为这些模板表示数据项,所以被称为DataTemplate。想了解DataTemplate,请继续收看‘D’is for DataTemplate。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值