SilverLight商业应用程序开发学习笔记(8)

适用于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与加载器之间的桥梁。关系如图所示:

image

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(); //设置当前页
} 
原文地址:https://www.cnblogs.com/qouoww/p/2493865.html