21 WPF数据视图
视图对象
当你绑定集合到ItemsControl,在幕后数据视图被安静地创造。视图位于数据源和绑定控件之间。数据视图是通往数据源的一个窗口。它跟踪当前项目,它支持诸如排序,过滤,和分组特征。这些特征独立于数据对象本身,意味着你能以不同的方式、在窗口的不同部分(或应用的不同部分)绑定相同的数据。例如,你能绑定相同的产品集合到两个不同的列表但是过滤他们显示不同的记录。
视图对象依赖于数据对象的类型。所有的视图派生自CollectionView,但是两个特殊的实现派生自CollectionView:ListCollectionView和BindingListCollectionView。这是它如何工作:
- 如果数据源实现IBindingList,一个BindingListCollectionView被创造。当你绑定一个ADO.NET DataTable时发生。
- 如果数据源没有实现IBindingList但是它实现IList,一个ListCollectionView被创造。当你绑定一个ObservableCollection,如同产品的列表。
- 如果你的数据源没有实现IBindingList或IList但是它实现IEnumerable,你获得一个基本CollectionView。
取回一个视图对象
为获得一个目前使用的视图对象,你使用System.Windows.Data.CollectionViewSource类的GetDefaultView()静态方法。当你调用GetDefaultView(),并传递数据源,就是你正使用的集合。这是一个例子,获得绑定到列表的产品集合的视图:
ICollectionView view = CollectionViewSource.GetDefaultView(lstProducts.ItemsSource);
GetDefaultView()方法总返回一个ICollectionView引用。你需要根据数据源转换视图对象到合适的类,可能是ListCollectionView或BindingListCollectionView。
var view = (ListCollectionView)
CollectionViewSource.GetDefaultView(lstProducts.ItemsSource);
用视图导航
视图对象决定列表项目的数目(Count属性)和获得当前的数据对象一个引用(CurrentItem)或当前的位置索引(CurrentPosition)。也能使用几个方法从一记录移动到另一个,诸如MoveCurrentToFirst(),MoveCurrentToLast(),MoveCurrentToNext(),MoveCurrentToPrevious(),和MoveCurrentToPosition()。
显示绑定产品数据的绑定文本框保持不变。他们只需要指明合适的属性,如下所示:
<TextBlock Margin="7">Model Number:</TextBlock>
<TextBox Margin="5" Grid.Column="1" Text="{Binding Path=ModelNumber}"></TextBox>
但是,这例子没有包括任何列表控件,所以你要控制导航。为简化生活,你能在你的窗口类添加一个成员变量,存储指向视图的一个引用:
private ListCollectionView view;
在这种情况下,代码转换视图到合适的视图类型(ListCollectionView)而不是使用ICollectionView接口。ICollectionView接口提供了大多数功能,但是它缺乏Count属性。
当窗口第一次加载,你能获得数据,放置它到窗口的DataContext,和存储一个引用指向视图:
var products = App.StoreDB.GetProducts();
this.DataContext = products; view = (ListCollectionView)
CollectionViewSource.GetDefaultView(this.DataContext);
view.CurrentChanged += new EventHandler(view_CurrentChanged);
第二行在DataContext中放置产品对象的完整集合。绑定控件将沿元素树向上搜索,直到他们发现这个对象。当然,你希望绑定表达式绑定到集合的当前项目,而不是绑定到集合本身,但是WPF足够聪明能自动地推算。它自动地提供他们当前项目,所以你不需要额外的代码的一个缝合。
前一个例子有一附加的代码语句。它连接一个事件处理器到视图的CurrentChanged事件。当事件发生,你能执行几个有用的行为,诸如前一个和下一个按钮依赖于当前位置可用或不可用,和在窗口底部的TextBlock显示当前位置。
private void view_CurrentChanged(object sender, EventArgs e)
{
lblPosition.Text = "Record " + (view.CurrentPosition + ).ToString() +
" of " + view.Count.ToString();
cmdPrev.IsEnabled = view.CurrentPosition > ;
cmdNext.IsEnabled = view.CurrentPosition < view.Count - ;
}
最后一步是写前一个和下一个按钮的逻辑。因为当这些按钮不能应用时,自动地不可用。你不需要考虑可能会移动到第一个项目之前或最后一个项目之后。
private void cmdNext_Click(object sender, RoutedEventArgs e)
{
view.MoveCurrentToNext();
}
private void cmdPrev_Click(object sender, RoutedEventArgs e)
{
view.MoveCurrentToPrevious();
}
你能添加一个组合框到窗口,用于直接跳到某一记录。
<ComboBox Name="lstProducts" DisplayMemberPath="ModelName"
Text="{Binding Path=ModelName}"
SelectionChanged="lstProducts_SelectionChanged"></ComboBox>
指定数据源:
lstProducts.ItemsSource = products;
默认情况下,ItemsControl的当前项目不与视图的当前项目同步。幸运地,有两个容易的方法解决问题。
第一个用传统的代码方式强制同步:
private void lstProducts_SelectionChanged(object sender, RoutedEventArgs e)
{
view.MoveCurrentTo(lstProducts.SelectedItem);
}
一个更简单解决方案是设置ItemsControl.IsSynchronizedWithCurrentItem为真。那样,目前选择项目自动地同步匹配视图的当前位置。
使用查询表帮助编辑
组合框能方便地编辑记录值。
例如,你可能有数据库一个字段接受几个预置值之一。在这种情况下,使用一个组合框,绑定它到合适的字段,在Text属性上使用一个绑定表达式。但是,填充组合框用容许的值,依靠设置它的ItemsSource属性指向你定义列表。并且如果你希望显示列表值一方式(例如,为文本)但是存储他们另一个方式(为数字编码),只要添加一个值转换器到你的Text属性绑定。
另一个情况是相关表。例如,你可能希望允许用户拾一个产品目录使用定义所有的目录列表。基本方法是相同的:设置Text属性绑定合适的字段,和用ItemsSource属性填充选项列表。如果你需要转换低层的IDs到更有意义的名字,使用一个值转换器。
用声明方式创造一个视图
你能在XAML标记以声明方式构造一个CollectionViewSource,和然后绑定CollectionViewSource到你的控件(诸如列表)。
从技术上,CollectionViewSource不是一个视图。它是一个帮助者类,允许你取回一个视图(使用GetDefaultView()方法)和一个工厂,能创造一个视图。
CollectionViewSource类的二最重要的属性是View,包裹视图对象,和Source,包裹数据源。CollectionViewSource也添加SortDescriptions和GroupDescriptions属性,这镜像同一地命名视图属性。当CollectionViewSource创造一个视图,它简单地传递这些属性的值到视图。
CollectionViewSource也包含一个Filter事件,你能处理执行过滤。这过滤工作方式等同于视图对象提供的过滤回调,除了它被定义为一个事件,所以你能容易地在XAML中挂钩上你的事件处理器。
例如,考虑前一个例子,使用价格范围这对产品分组。这是你如何以声明方式定义转换器和CollectionViewSource:
<local:PriceRangeProductGrouper x:Key="Price50Grouper" GroupInterval="50"/>
<CollectionViewSource x:Key="GroupByRangeView">
<CollectionViewSource.SortDescriptions>
<component:SortDescription PropertyName="UnitCost" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="UnitCost"
Converter="{StaticResource Price50Grouper}"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
注意,SortDescription类不是WPF名字空间。为了使用它,你需要填加下面的名字空间别名:
xmlns:component="clr-namespace:System.ComponentModel;assembly=WindowsBase"
一旦你建立CollectionViewSource,你能绑定它到你的列表:
<ListBox ItemsSource="{Binding Source={StaticResource GroupByRangeView}}" ... >
似乎列表框控件绑定到CollectionViewSource,而不是CollectionViewSource暴露的视图(这被存储在CollectionViewSource.View属性)。但是,WPF数据绑定对于CollectionViewSource一个特殊的例外。当你使用它在一个绑定表达式,WPF请求CollectionViewSource创造它的视图,然后绑定视图到合适的元素。
声明式的方法没有真正地节省你任何工作。你仍然需要在运行时用代码取回数据。不同的是现在你的代码必须传递数据沿着到CollectionViewSource而不是直接提供它到列表:
var products = App.StoreDB.GetProducts();
var viewSource = (CollectionViewSource)
this.FindResource("GroupByRangeView");
viewSource.Source = products;
可选地,你能使用XAML标记创造产品集合作为一个资源。然后你能以声明方式绑定CollectionViewSource到你的产品集合。但是,你仍然需要使用代码填充你的产品集合。
过滤、排序、和分组
视图跟踪数据对象集合的当前位置。这是一个重要的任务,和发现(或改变)当前项目是使用视图的最普遍原因。
视图也提供若干可选的特征那允许你管理项目的全体集合。在下几节中,你将会看到你能如何使用一个视图过滤你的数据项目(暂时地隐藏那些你不希望看见),你能如何使用它应用排序(改变数据项目顺序),和你能如何使用它应用分组(创造能被独立地导航子集合)。
过滤集合
过滤允许你显示满足特定条件的一个子集。当带有一个集合作为数据源工作时,你使用视图对象的Filter属性设置过滤。
Filter属性的实现有点笨拙。它接受一个Predicate委托指向一个自定义过滤方法(你创造)。这是一个例子,你能如何连接视图到方法FilterProduct():
var view = (ListCollectionView)
CollectionViewSource.GetDefaultView(lstProducts.ItemsSource);
view.Filter = new Predicate<object>(FilterProduct);
笨拙之处在于你只能使用Predicate<object>类型,而不是Predicate<Product>类型。
这是一个简单的过滤器方法,只允许单价高于100的产品:
public bool FilterProduct(Object item)
{
var product = (Product) item;
return (product.UnitCost > );
}
使用匿名委托,定义内联的过滤方法:
var view = (ListCollectionView)
CollectionViewSource.GetDefaultView(lstProducts.ItemsSource);
view.Filter = delegate(object item)
{
Product product = (Product)item;
return (product.UnitCost > );
};
尽管这是一个整洁的,优雅的方法,在更复杂的过滤器场景下,你更可能创造一个专用的过滤类。那是因为在这些情况下,你经常需要过滤使用几个不同的准则,和后来你可能希望能修改过滤准则。
过滤类包裹过滤准则和执行过滤的回调方法。这里是一个极端地简单的过滤类,过滤掉单价小于最小价格的产品:
public class ProductByPriceFilter
{
public decimal MinimumPrice
{
get; set;
}
public ProductByPriceFilter(decimal minimumPrice)
{
MinimumPrice = minimumPrice;
}
public bool FilterItem(Object item)
{
var product = item as Product;
if (product != null)
{
return (product.UnitCost > MinimumPrice);
}
return false;
}
}
这里是创造ProductByPriceFilterer和使用它应用最小价格过滤的代码:
private void cmdFilter_Click(object sender, RoutedEventArgs e)
{
decimal minimumPrice;
if (Decimal.TryParse(txtMinPrice.Text, out minimumPrice))
{
var view =
CollectionViewSource.GetDefaultView(lstProducts.ItemsSource)
as ListCollectionView;
if (view != null)
{
var filter =
new ProductByPriceFilter(minimumPrice);
view.Filter = new Predicate<object>(filter.FilterItem);
}
}
}
你可能想创造不同的过滤器对于过滤不同的数据的类型。例如,你可能计划创造(和重用)一个MinMaxFilter,一个StringFilter,等等。无论如何,通常更有帮助的是对于每个窗口创造一个单个的过滤类。那是因为你不能链一个以上过滤在一起。
如果你希望不重新创造ProductByPriceFilter对象情况下,修改过滤,你需要在你的窗口类存储一个成员变量引用过滤对象。然后你能修改过滤属性。但是,你也需要调用视图对象的Refresh()方法强迫列表被重新过滤。这里是一些代码,当包含最小价格的文本框的TextChanged事件发生时,调整过滤设置:
private void txtMinPrice_TextChanged(object sender, TextChangedEventArgs e)
{
var view =
CollectionViewSource.GetDefaultView(lstProducts.ItemsSource)
as ListCollectionView;
if (view != null)
{
decimal minimumPrice;
if (Decimal.TryParse(txtMinPrice.Text, out minimumPrice) &&
(filter != null))
{
filter.MinimumPrice = minimumPrice;
view.Refresh();
}
}
}
最后,依靠设置Filter属性为空,你能完全清除过滤器。
view.Filter = null;
过滤DataTable
详见656页。
排序
最简单的方法是基于每个数据项目一个或多个属性值分类。每个SortDescription代表一个属性:
var view = CollectionViewSource.GetDefaultView(lstProducts.ItemsSource);
view.SortDescriptions.Add(
new SortDescription("ModelName", ListSortDirection.Ascending));
也可以自定义排序规则:
public class SortByModelNameLength : IComparer
{
public int Compare(object x, object y)
{
var productX = (Product)x;
var productY = (Product)y;
return productX.ModelName.Length.CompareTo(productY.ModelName.Length);
}
}
自定义规则连接到视图:
var view = (ListCollectionView)
CollectionViewSource.GetDefaultView(lstProducts.ItemsSource);
view.CustomSort = new SortByModelNameLength();
分组
正如排序,你能用容易的办法分组(基于单个的属性值)或困难地办法(使用一个自定义回调)。
简单分组法:
var view = CollectionViewSource.GetDefaultView(lstProducts.ItemsSource);
view.GroupDescriptions.Add(new PropertyGroupDescription("CategoryName"));
当你使用分组,你的列表为每个分组创造一个独立的GroupItem对象,并且它添加这些GroupItem对象到列表。GroupItem是一个内容控件,所以每个GroupItem持有合适的容器(好像ListBoxItem对象),容器带有你的实际的数据。显示你的分组的关键是格式化GroupItem元素所以它脱颖而出。
你能使用一个样式,应用格式化到一个列表中所有的GroupItem对象。但是,你可能希望不仅仅格式化—例如,你可能希望显示组标头,这要求一个模板的帮助。幸运地,ItemsControl类使两个任务容易,通过它的ItemsControl.GroupStyle属性,这提供一个GroupStyle对象的集合。尽管名字,GroupStyle类不是一个样式。它只是一个方便的包那包裹几个有用的设置对于配置你的GroupItem对象。
GroupStyle属性:
| 名字 | 描述 |
| ContainerStyle | 设置被应用于GroupItem样式,对于每个分组生成。 |
| ContainerStyleSelector | 代替使用ContainerStyle,你能使用ContainerStyleSelector提供一个类那选择正确样式使用,基于分组。 |
| HeaderTemplate | 允许你创造一个模板显示每个分组的开始内容。 |
| HeaderTemplateSelector | 代替使用HeaderTemplate,你能使用HeaderTemplateSelector提供一个类那选择正确标头模板使用,基于分组。 |
| Panel | 允许你改变被用于持有分组的模板。例如,你能使用一个WrapPanel代替标准StackPanel创造一个列表平铺分组从左到右和然后向下。 |
这个例子,只设置每个分组之前的标头。
为添加一个分组标头,你需要设置GroupStyle.HeaderTemplate。你能用一个普通的数据模板填充这属性。你能使用元素的任意组合和你模板内部的数据绑定表达式。
但是,存在一诀窍。当你写你的绑定表达式,你不是绑定你列表的数据对象(在这种情况下,Product对象)。而是,你绑定分组的PropertyGroupDescription对象。那意味着如果你希望为了那个分组显示字段值,你需要绑定PropertyGroupDescription.Name属性而不是Product.CategoryName。
这是完整的模板:
<ListBox Name="lstProducts" DisplayMemberPath="ModelName">
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold"
Foreground="White" Background="LightGreen"
Margin="0,5,0,0" Padding="3"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>
</ListBox>
ListBox.GroupStyle属性实际上是一个GroupStyle对象的集合。这允许你添加多个分组的水平。为做如此,你需要添加一个以上PropertyGroupDescription(顺序你希望你的分组和子组应用)和然后添加一个匹配GroupStyle对象格式每个水平。
你可能希望使用分组协同排序。如果你希望分类你的组,只确保第一SortDescription你使用分类基于分组字段。下列代码分类目录按字母顺序依靠目录名字和然后分类每个产品依靠模型名字顺序。
view.SortDescriptions.Add(new SortDescription("CategoryName",
ListSortDirection.Ascending));
view.SortDescriptions.Add(new SortDescription("ModelName",
ListSortDirection.Ascending));
分组范围
本节讲基于数值的范围分组,依靠的是值转换器,将不同值转换为一个相同值。详见661页。
分组和虚拟化
见663页。
Live Shaping
见663页。
21 WPF数据视图的更多相关文章
- WPF数据视图学习
当你绑定集合到ItemsControl,数据视图被安静地在幕后创造.视图位于数据源和绑定控件之间.数据视图是通往数据源的一个窗口.它跟踪当前项目,它支持诸如排序,过滤,和分组特征.这些特征独立于数据对 ...
- [Aaronyang] 写给自己的WPF4.5 笔记8[复杂数据处理三步曲,数据视图精讲1/3]
真的好累了 ,笑了.做回自己吧 ------------- Aaronyang技术分享 www.ayjs.net 博文摘要: 详细介绍了WPF中视图的种类和开始学之前的准备工作 ...
- 使用MVVM DataTemplate在WPF XAML视图之间切换
原文 使用MVVM DataTemplate在WPF XAML视图之间切换 更新:这个技术的改进版本,一个不创建视图,可以在以下链接找到: http://www.technical-recipes.c ...
- laravel5.5框架中视图间如何共享数据?视图间共享数据的两种方法
laravel框架中视图间共享数据有两种,一种是用视图门面share()方法实现,另一种是用视图门面composer() 方法实现,那么,两种方法的实现究竟是怎样的呢?让我们来看一看接下来的文章内容. ...
- SharePoint 2013 Designer系列之数据视图筛选
在SharePoint中,我们经常需要对列表进行简单的筛选,这时,数据视图就有作用了,我们可以定制对于字段的筛选,来进行展示:特别的,筛选不同于搜索,并没有对于附件或者文档的全文检索,如果需要全文检索 ...
- SharePoint 2013 Designer系列之数据视图
在SharePoint使用中,数据展示是一块很重要的部分,很多时候我们会采用webpart的形式,但是有一些情况,我们不必使用开发,仅需使用Designer即可,下面让我简单介绍下数据视图的使用. 1 ...
- SharePoint开发 - Excel数据导入到SharePoint自定义列表(数据视图方式)
博客地址 http://blog.csdn.net/foxdave 本篇讲解一个有些新颖的SharePoint实例应用,给甲方做过项目的都有过体会,数据太多了,客户有Excel,要求实现批量导入. 效 ...
- WPF窗体视图中绑定Resources文件中字符串时,抛出:System.Windows.Markup.StaticExtension
问题描述: 在Resources.resx定义了一个静态字符串字段Title,并在WPF窗体视图中绑定为窗体的标题: Title="{x:Static local:Resources.Tit ...
- SharePoint数据视图无法打开
最近在折腾SharePoint,之前列表常用的“数据视图”居然不能打开,提示“没有安装Sharepoit foundation 数据兼容组件”如图: 上网G下.度下有说要删除注册表.要安装office ...
随机推荐
- 零基础入门学习Python(20)--函数:内嵌函数和闭包
知识点 global关键字 使用global关键字,可以修改全局变量: >>> count = 5 >>> def Myfun(): count = 10 prin ...
- linux traceroute-显示数据包到主机间的路径
博主推荐:更多网络测试相关命令关注 网络测试 收藏linux命令大全 traceroute命令用于追踪数据包在网络上的传输时的全部路径,它默认发送的数据包大小是40字节. 通过traceroute我 ...
- ubuntu14.04 Google Chrome can not be run as root
问题如下图:
- Python之模块和包的创建与使用
一.模块的概念 在计算机的开发过程中,随着程序代码越写越多,在一个文件里代码就越来越长,越来越不容易维护. 为了编写可维护的代码,我们把很多函数分组,放在不同的文件里面,这样,每个文件包含的代码就相对 ...
- 初学数位DP
所谓数位dp,字面意思就是在数位上进行dp,数位的含义:一个数有个位.十位.百位.千位.等等,数的每一位就是数位. 数位DP一般应用于: 求出给定区间[A,B]内,符合条件P[i]的数 i 的个数. ...
- 关于.NET玩爬虫这些事 【初码干货】
这几天在微信群里又聊到.NET可以救中国但是案例太少不深的问题,我说.NET玩爬虫简直就是宇宙第一,于是大神朱永光说,你为何不来写一篇总结一下? 那么今天就全面的来总结一下,在.NET生态下,如何玩爬 ...
- jQuery_DOM学习之------包裹元素的方法
1..wrap( ):在集合中匹配的每个元素周围包裹一个HTML结构 简单的看一段代码: <span>连接文字</span> 给span元素增加一个a包裹 $('span'). ...
- [K/3Cloud] 创建一个单据转换插件
概念: 创建一个业务单据转换插件,在单据转换的各个时点干预单据转换的相关逻辑控制. 示例: 新建一个类,继承自单据转换插件基类Kingdee.BOS.Core.Metadata.ConvertElem ...
- 咏南 DELPHI DATASNAP LINUX中间件
咏南 DATASNAP LINUX中间件 咏南 DATASNAP LINUX中间件,一套源码,同时支持WINDOWS和LINUX操作系统. 基于DELPHI 10.2 TOKYO开发 使用FIRE ...
- 【转】配置windows路由表,使电脑同时连接内网外网方法
1. 公司内部,内网和外网的网关不一样,怎么样让电脑可以同时上内网和外网呢? 来一张不相关的磁盘结构图: ----------------------------------------------- ...