WPF_16_数据视图

数据视图在后台工作,用于协调绑定数据的集合。使用数据视图,可添加导航逻辑并实现过滤,排序以及分组。

View对象

当将集合绑定到ItemsControl控件时,会自动在后台创建数据视图-位于数据源和绑定的控件之间。数据视图支持排序,过滤以及分组,这些功能和数据对象本身是相互独立的。例如,可以将同一数据集合绑定到两个不同的列表,并对数据进行过滤以显示不同的记录。

所有视图都继承自 CollectionView 类,它有两个子类 ListCollectionView 和 BindingListCollectionView.

  • 如果数据源实现了 IBindingList 接口,创建 BindingListCollectionView 视图。当绑定DataTable对象时会创建该视图
  • 如果数据源实现了 IList 接口,创建 ListCollectionView 视图。当绑定ObservableCollection集合时会创建该视图
  • 如果没有实现IBindingList或IList接口,但实现了IEnumerable接口,创建CollectionView视图。

使用代码检索视图对象

ListCollectionView view = (ListCollectionView)CollectionViewSource.GetDefaultView(lstProducts.ItemsSource);

也可以在XAML标记中以声明方式构建CollectionViewSource对象,然后绑定到控件。

<CollectionViewSource x:Key="GroupByRangeView">
    <CollectionViewSource.SortDescriptions>
        <!--SortDescription类不是WPF名称空间中的类-->
        <component:SortDescription PropertyName="UnitCost" Direction="Ascending"/>
    </CollectionViewSource.SortDescriptions>
    <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="UnitCost" Converter="{StaticResource PriceConverter}"/>
    </CollectionViewSource.GroupDescriptions>
</CollectionViewSource>

<!--绑定到列表中-->
<ListBox ItemSource="{Binding Source={StaticResource GroupByRangeView}}"/>

但声明的方式仍需要编写代码来检索数据:

CollectionViewSource viewSource = (CollectionViewSource)this.FindResource("GroupByRangeView");
viewSource.Source = products;

视图提供了一系列的方法和属性:

  • Count - 列表中的项数
  • CurrentItem - 当前数据对象的引用
  • CurrentIndex - 当前位置索引
  • MoveCurrentToFirst()
  • MoveCurrentToLast()
  • MoveCurrentToNext()
  • MoveCurrentToPrevious()
  • MoveCurrentToPosition()

过滤,排序与分组

过滤集合

当将集合用作数据源时,可使用 Filter 属性设置过滤器。

private void SetFilter()
{
    viewSource.Filter = new Predicate<object>(FilterProduct);
}
// 过滤事件
public bool FilterProduct(object item)
{
    Product product = (Product)item;
    return (product.UnitCost > 100);
}
// 为视图调用 Refresh() 方法,从而强制性地重新过滤列表
public void RefreshView()
{
    viewSource.Refresh();
}

过滤DataTable对象

DataTable对象都与一个DataView对象相关联,DataTable对象使用BindingListCollectionView视图不支持 Filter 属性,而是提供了 CustomFilter 属性。CustomFilter属性不做任何工作,只是接收指定的过滤字符串,并使用这个过滤字符串设置 DataView.RowFilter 属性。

RowFilter 将基于字符串的过滤器表达式作为参数,类似于Select查询中用于构造 WHERE 子句的 SQL 代码段。

var view = CollectionViewSource.GetDefaultView(lstProducts.ItemsSource) as BindingListCollectionView;
view.CustomFilter = "UnitCost > " + minimumPrice.ToString();

排序

// 按照某个属性进行 升序 或 降序
view.SortDescriptions.Add(
    new SortDescription("ModelName", ListSortDirection.Ascending)
);

// 自定义排序
// 需要实现 IComparer 接口
view.CustomSort = new SortByModelNameLength();

分组

和排序一样,可以根据单个属性值进行分组,也可以使用自定义的回调函数进行分组。

view.GroupDescriptions.Add(new PropertyGroupDescription("CategoryName"));

当使用分组时,列表为每个分组创建了单独的GroupItem对象,并且为列表添加了这些GroupItem对象。使用样式可为列表中的所有GroupItem对象应用格式。ItemsControl.GroupStyle类并不是样式,只是一个简单的包,属性如下:

属性 说明
ContainerStyle 设置被应用到为每个分组生成的GroupItem元素样式
ContainerStyleSelector 提供一个类,根据分组选择准备使用的正确样式
HeaderTemplate 为每个分组开头显示内容创建模板
HeaderTemplateSelector 提供一个类,根据分组选择准备使用的正确的头模板
Panel 改变用于包含分组的模板
<ListBox Name="lstProducts" DisplayMemberPath="ModelName">
    <ListBox.GroupStyle>
        <GroupStyle>
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Path=Name}" Margin="0,5,0,5"/>
                </DataTemplate>
            </GroupStyle.HeaderTemplate>
        </GroupStyle>
    </ListBox.GroupStyle>
</ListBox>

这种根据某个属性进行简单分组,需要这个字段多条记录有同一个值。但如果想进行范围分组,就需要提供值转换器。

public class PriceRangeProductGrouper : IValueConverter
{
    public int GorupInterval { get; set; }
    
    public object Conveert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        decimal price = (decimal)value;

        if( price < GroupInterval)
        {
            return String.Format(culture, "Less than {0:C}", GroupInterval);
        }
        {
            int intval = (int)price / GroupInterval;
            int lowerLimit = interval * GroupInterval;
            int upperLimit = (interval + 1) * GroupInterval;
            return String.Format(culture, "{0:C} to {1:C}", lowerLimit, upperLimit);
        }
    }
}

在使用范围分组时,首先根据价格对产品进行排序,否则会根据它们在列表中的位置进行分组。

view.SortDescriptions.Add(new SortDescription("UnitCost", ListSortDirection.Ascending));
var gouper = new PriceRangeProductGrouper();
grouper.GroupInterval = 50;
view.GroupDescriptions.Add(new PropertyGroupDescription("UnitCost", grouper));

在控件使用分组时不启用虚拟化,WPF使用 VirtualizingStackPanel.IsVirtualizingWhenGrouping 属性纠正了这个问题。

<ListBox VirtualizingStackPanel.IsVirtualizingWhenGrouping="True"/>

如果某个编辑器将价格降低至过滤条件需要的最低值以下,理论上这条记录应该从视图中消失,但除非强制执行更新(调用ICollectionViewSource.Refresh()),否则看不到任何更改。WPF 4.5引入了实时成型的功能,监视特定属性的变化,如果发现变化就会触发刷新操作。实时成型需要满足以下三项标准:

  • 数据对象必须实现 INofityPropertyChanged
  • 集合必须实现 ICollectionVeiwLiveShaping
  • 明确启用实时成型

实时成型增加了额外的开销,因此设计了三个属性 IsLiveFiltering, IsLiveSorting, IsLiveGrouping.这样可以忽略不重要的属性,确保获得最佳性能。

view.IsLiveFiltering = true;
view.LiveFilteringProperties.Add("UnitCost");

我的公众号

posted @   RisingWaves  阅读(132)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· 地球OL攻略 —— 某应届生求职总结
点击右上角即可分享
微信分享提示