适用于SilverLight商业应用程序的几个重要的类
尽管使用DomainDataSource控件很方便,但是使用该控件使得显示层与业务层呈紧耦合状态,因此在一般分层的应用开发中,很少直接使用DomainDataSource控件,而是选择集合视图作为显示层与数据层之间的桥梁,于是如下一些类就应运而生。这些类有两种,一种是完全在客户端执行逻辑,如LisCollectionView集合视图和PagedCollectionView集合视图,一种是完全在服务器端执行逻辑。在服务器端执行逻辑的好处是可以根据需要向客户端发送数据,从而减少了网络的流量。(比如DomainCollection的分页是在服务器端执行的,在网络传输的只是分页后指定页面的数据)。下面分别用实例给出各个类的用法。
在进行深入讨论之前,首先创建一个视图模型类并绑定到DataGrid控件上,后面的各系列操作都是在此视图模型类下展开的:
1)在SilverLight项目的View文件夹下创建一个视图:ProductListView.xaml;
2)在View文件夹下创建一个新类:ProductListViewModel.cs
using System.Collections.Generic; using AdventureWorks.Web.Services; using AdventureWorks.Web.Models; namespace AdventureWorks.Views { public class ProductListViewModel { public IEnumerable<ProductSummary> Products { get; set; } public ProductListViewModel() { ProductSummaryContext context = new ProductSummaryContext(); var qry = context.GetProductSummaryListQuery(); var op = context.Load(qry); Products = op.Entities; } } }
3)在视图的构造器(后置代码)里创建视图模型类的实例并赋给视力瓣DataContext属性:
public ProductListView() { InitializeComponent(); this.DataContext = new ProductListViewModel(); }
4)将视图中的控件绑定到视图模型上:
<sdk:DataGrid ItemsSource="{Binding Products}" />
1、ObservableCollection<T>泛型类
SIlverlight支持许多通用的泛型类,包括List,Dictionary,LinkedList,Stack和Queue。但是最重要的泛型类还是ObservableCollecton<T>泛型类,该类实现了INotifyCollectionChanged接口,暴露CollectionChanged事件,在向集合添加或从集合移除项目时会引发该事件。当ListBox,DataGrid,ComboBox控件的ItemsSource属性绑定到这种泛型类的实例以后,由于CollectionChanged事件的作用,集合变更时会自动更新控件里的相应项目。可以实现显示与业务逻辑分离,解除之间的紧耦合。
2、ListCollectionView/EnumerableCollectionView集合视图
ListCollectionView/EnumerableCollectionView集合视图可以在内存中(在客户端)筛选,排序和分组所承载的集合。(但这两个集合视图不支持数据分页)这两个集合视图不能直接实例化(因为其构造器为internal),需要CollectionViewSource类充当集合视图的“代理”,然后才能实例化。首先需要定义CollectionViewSource为资源,将一个集合赋值给其Source属性。这样该资源的View属性就可以作为ListCollectionView/EnumerableCollectionView集合视图使用,然后使用Filter,GroupDescriptions和SortDescriptions属性实现筛选,排序和分组功能。ListCollectionView/EnumerableCollectionView集合视图适用于如下情况:
- 已经将所有数据加载到客户端
- 需要将集合封装到集合视图以便在XAML中使用
- 不需要对UI的数据进行分页处理
3、PagedCollectionView集合视图
PagedCollectionView集合视图可以直接实例化,支持数据分页。该集合视图适合于如下场景:
- 已经将所有数据加载到客户端
- 需要在View model类中控制筛选、排序、分组和分页;
- 需要对UI的数据进行分页
实例:封装数据到PagedCollectionView,其实现步骤首先是在视图模型类里将集合封装为PagedCollectionView,然后将其作为视图模型暴露的属性以便进行绑定。
1)在项目中添加对System.Windows.Data.dll 程序集的引用;
2)在ProductListViewModel类上添加对System.Windows.Data的引用;
3)向类中添加新的属性,类型为PagedCollectionView,命名为ProductCollectionView:
public PagedCollectionView ProductCollectionView { get; set; }
4)在视图模型的构造函数里将集合封装为PagedCollectionView,将结果赋值给ProductCollectionView属性:
public ProductListViewModel() { ProductSummaryContext context = new ProductSummaryContext(); var qry = context.GetProductSummaryListQuery(); var op = context.Load(qry); Products = op.Entities; ProductCollectionView = new PagedCollectionView(Products); }
5)在ProductListView.xaml视图里,就不需要在构造函数里实例化视图模型类了,而是直接将控件绑定到视图模型的PagedCollectionView类型的属性ProductCollectionView上:
<sdk:DataGrid ItemsSource="{Binding ProductCollectionView}" />
实例二、使用PagedCollectionView进行筛选操作:需要给PagedCollectionView对象的Filter属性指定回调方法,Filter属性的返回值为bool类型的一个委托,当其值设为一个委托时,该委托会在回调中对源进行排序,其返回的Bool值就表示显示还是隐藏。每次筛选执行后就根据Filter的返回值确定是否调用Refresh方法。在下面的示例里,在视图模型类里创建了一个SearchText属性,用以绑定到UI的查询文本框中。当SearchText属性变更时,就执行筛选回调方法刷新PagedCollectionView对象,这样前端的显示就随之进行了更新:
1)添加SearchText属性,与自动属性不同之处是在每次设置后都要执行PagedCollectionView对象的Refresh方法:
private string _searchText = ""; public string SearchText { get { return _searchText; } set { _searchText = value; ProductCollectionView.Refresh(); } }
2)在视图模型的构造函数里,为ProductCollectionView集合视图指定筛选条件:
ProductCollectionView = new PagedCollectionView(Products);
ProductCollectionView.Filter = item =>
((ProductSummary)item).Name.IndexOf(SearchText,
StringComparison.InvariantCultureIgnoreCase) != -1;
3)在视图里设置TextBox的绑定:
<TextBox Name="SearchTextBox" Grid.Column="1" Margin="0, 3" Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
有关PagedCollectionView的动态多条件选择,参考http://www.cnblogs.com/626498301/archive/2010/08/18/1801974.html。
示例三:使用PagedCollectionView实现排序功能:需要实例化一个SortDescription对象,然后将该对象添加到PagedCollectionView的SortDescriptions集合属性即可:
1)在视图模型类中添加引用:
using System.ComponentModel;
2)实例化SorDescription对象,并添加到SortDescriptions集合:
ProductCollectionView = new PagedCollectionView(Products); SortDescription sortBy = new SortDescription("Name", ListSortDirection.Ascending); ProductCollectionView.SortDescriptions.Add(sortBy);
示例四:使用PagedCollectionView实现分组功能:需要实例化一个PropertyGroupDescription对象,然后将该对象添加到PagedCollectionView的GroupDescriptions集合属性即可:
1)在视图模型类中添加引用:
using Sysetm.Windows.Data;
2)在实例化PagedCollectionView对象后,以下列代码用Model属性进行分组:
ProductCollectionView = new PagedCollectionView(Products); PropertyGroupDescription groupBy = new PropertyGroupDescription("Model"); ProductCollectionView.GroupDescriptions.Add(groupBy);
在分组操作中可以提供值转换器作为PropertyGroupDescription构造器的参数,用于分组;这在使用外键数据进行分组时特别有用。比如想要使用Category来分组Products,但是Products对象只包含Category的Id值,如果使用Id作分组,分组标识就成了Id值,这显然不是我们想要的。如果想要使用Category的名称来排遣序,就需要编写一个值转换器,将CategoryId转换为CategoryName。
示例五:使用PagedCollectionView实现分页:只需要将PagedCollectionView对象绑定到DataPager控件的Source属性即可:
<sdk:DataPager PageSize="30" Source="{Binding ProductCollectionView}" />
4、DomainDataSourceView集合视图
DomainDataSourceView集合视图由DomainDataSource通过其DataView属性暴露。无法通过代码创建该集合视图,只能通过DomainDataSource控件创建。DomainDataSourceView集合视图实现的筛选,排序,分页等功能是在服务器端完成的。
5、DomainCollectionView集合视图
DomainCollectionView集合视图由WCF RIA Service ToolKit引入,与DomainDataSource所表现的行为相同(通过RIA服务获取数据,在服务器端筛选,排序和分页数据),不同之处在于实现了视图与域上下文类的分离,这样视图无需要知道数据是如何获取的。需要引用Microsoft.Windows.Data.DomainServices.dll程序集。
使用DomainCollectionView集合视图需要三个关键组件:DomainCollectionView集合视图本身,需要封装的源集合以及“Loader(加载器)”,后者包括与服务器交互和更新源集合数据的类。DomainCollectionView 充当了UI与加载器之间的桥梁。关系如图所示:
WCF RIA Services Toolkit 已经实现了默认的加载器:DomainCollectionViewLoader,该加载器以方法委托作为构造器参数,当数据需要加载或加载完成时调用这些方法;
DomainCollectionView 集合视图处理如下场景:
- 使用RIA服务从服务器中获取数据
- 使用MVVM设计模式
实例:从DomainCollectionView获取数据,步骤如下:
1)确保安装了WCF RIA Services Toolkit,并在SIlverlight项目中添加了Microsoft.Windows.Data.DomainServices.dll程序集的引用;
2)在ProductListViewModel类上添加如下引用:
using System.ServiceModel.DomainServices.Client;
using Microsoft.Windows.Data.DomainServices;
3)在类中添加类型为DomainCollectionView的属性:ProductCollectionView:
public DomainCollectionView ProductCollectionView { get; set; }
4)在视图模型类中添加如下代码,确保内存中的上下文实例唯一:
private ProductSummaryContext _context = new ProductSummaryContext();
5)在视图模型类中添加如下两个方法:
private LoadOperation<ProductSummary> LoadProductSummaryList() { EntityQuery<ProductSummary> query = _context.GetProductSummaryListQuery(); return _context.Load(query); } private void OnLoadProductSummaryListCompleted(LoadOperation<ProductSummary> op) { if (op.HasError) { // NOTE: You should add some logic for handling errors here, and mark // the error as handled. // op.MarkErrorAsHandled(); } else if (!op.IsCanceled) { ((EntityList<ProductSummary>)Products).Source = op.Entities; } }
6)在视图模型的构造器中添加如下代码:
public ProductListViewModel() { Products = new EntityList<ProductSummary>(_context.ProductSummaries); var collectionViewLoader = new DomainCollectionViewLoader<ProductSummary>( LoadProductSummaryList, OnLoadProductSummaryListCompleted); ProductCollectionView = new DomainCollectionView<ProductSummary>(collectionViewLoader, Products); ProductCollectionView.Refresh(); }
上述代码是创建DomainCollectionView的范式,注意加粗字体的内容以及各个方法传递的参数类型,特别传递的委托参数类型。
7)可以直接在视图中将ProductCollectionView属性绑定到控件的数据源属性:
<sdk:DataGrid ItemsSource="{Binding ProductCollectionView}" />
实例二:使用DomainCollectionView实现筛选:与PagedCollectionView对象类似,DomainCollectionView对象也有一个Filter属性,指定该属性一个实现了筛选逻辑的委托就可以实现筛选作业。但是这种筛选只对客户端的项目有效,如果要想在服务器端进行筛选操作,需要在DomainCollectionView的加载器里添加Where查询字句。实现方法如下:
1)与前述例子相类似,先在视图模型类中添加一个SearchBox属性以绑定到查询文本框中,并且在获得数据后刷新集合视图:
private string _searchText = ""; public string SearchText { get { return _searchText; } set { _searchText = value; ProductCollectionView.Refresh(); } }
2)在LoadProductSummary方法里添加Where条件字句以在服务器端筛选数据,注意对私有字段的访问:
private LoadOperation<ProductSummary> LoadProductSummaryList() { EntityQuery<ProductSummary> query = _context.GetProductSummaryListQuery(); query = query.Where(x => x.Name.Contains(_searchText)); return _context.Load(query); }
3、绑定文本框到SearchText属性上:
<TextBox Name="SearchTextBox" Grid.Column="1" Margin="0, 3" Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
示例三:使用DomainCollectionView进行排序:与PagedCollectionView的配置方法类似,也是需要实例化一个SortDescription对象,然后将该对象添加到DomainCollectionView的SortDescriptions集合属性里:这些操作是在客户端完成的,如果想在服务器端实现排序也是可以的,另见分页部分的介绍;
1)添加如下引用:using System.ComponentModel;
2)实现排序功能:
ProductCollectionView = new DomainCollectionView<ProductSummary>(collectionViewLoader, Products); SortDescription sortBy = new SortDescription("Name", ListSortDirection.Ascending); ProductCollectionView.SortDescriptions.Add(sortBy);
示例四:使用DomainCollectionView实现分组功能:需要实例化一个PropertyGroupDescription对象,然后将该对象添加到DomainCollectionView的GroupDescriptions集合属性即可:
1)在视图模型类中添加引用:
using Sysetm.Windows.Data;
2)在实例化PagedCollectionView对象后,以下列代码用Model属性进行分组:
ProductCollectionView = new DomainCollectionView<ProductSummary>(collectionViewLoader, Products); PropertyGroupDescription groupBy = new PropertyGroupDescription("Model"); ProductCollectionView.GroupDescriptions.Add(groupBy);
示例五:使用DomainCollectionView实现分页,好处是只向服务器请求所需要的数据,从而大幅度减少了网络流量;实现这一功能需要使用EntityQuery类的扩展方法:SortBy,PageBy以及SortAndPageBy。实现方法与步骤如下:
1)将DataPger控件的Source属性绑定到DomainCollectionView对象上:
<sdk:DataPager PageSize="30" Source="{Binding ProductCollectionView}" />
2)修改LoadProductSummaryList方法,确保在将查询传递到域下下文的Load方法之前,使用SortAndPageBy扩展方法将集合视图的状态调整为query:
private LoadOperation<ProductSummary> LoadProductSummaryList() { EntityQuery<ProductSummary> query = _context.GetProductSummaryListQuery(); query = query.SortAndPageBy(ProductCollectionView); return _context.Load(query); }
3)为了完全实现分页行为,DataPager控件需要知道全部对象的数量。可以通过将查询对象的IncludeTotalCount属性为True来显示地通知服务器计算总记录数:
private LoadOperation<ProductSummary> LoadProductSummaryList() { EntityQuery<ProductSummary> query = _context.GetProductSummaryListQuery(); query = query.SortAndPageBy(ProductCollectionView); query.IncludeTotalCount = true; return _context.Load(query); }
4)获得服务器响应的同时,需要通过SetTotalItemCount将总记录数设置到DomainCollectionView对象上:
private void OnLoadProductSummaryListCompleted(LoadOperation<ProductSummary> op) { if (op.HasError) { // NOTE: You should add some logic for handling errors here op.MarkErrorAsHandled(); } else if (!op.IsCanceled) { ((EntityList<ProductSummary>)Products).Source = op.Entities; if (op.TotalEntityCount != -1) ProductCollectionView.SetTotalItemCount(op.TotalEntityCount); } }
5)确保在DomainCollectionView对象上设置了至少一个Sort Description,否则不同分页间导航会抛出异常;
6)将对Refresh方法的调用用如下代码替换:
using (ProductCollectionView.DeferRefresh())
{
ProductCollectionView.PageSize = 30; //设置page size
ProductCollectionView.MoveToFirstPage(); //设置当前页
}