在WinForm或者WebForm中我们有一大批的Grid控件供我们使用,DataGridView,GridView,Repeater等等,这样的网格数据空间给我们提供了极大的方便去让数据以可定义的方式显示并提供诸如导航,分页,排序,过滤,数据更新等附加操作 ,而程序员所需要付出的却很少。但在WPF中我们通常并不具备这么优越的网格控件,而要做到这些除了用Grid.RowDefinitions和Grid.ColumnDefinitions配合一起造出一个Grid,或者利用ListView控件的ListView.GridView视图,我们好像别无选择。而且对于这些的使用并不具备像WinForm中那么强大的功能,更多的需要程序员去控制数据的展现并提供附加操作的实现。微软新近在CodePlex上发布了WPF Toolkit,它就提供了WPF版本的DataGrid,一如既往的强大和易用,今天我们就一起来体验一下这个强大的控件。
1. Start with DataGrid
WPF开发团队专门重写了一套DataGrid让其可以运行于WPF之上,在http://www.codeplex.com/wpf 你可以获得这个ToolKit的源代码及其DLL文件,具体是如何写这些控件的,作为大家对WPF感兴趣的,您需要去研究一下。因为很值得去研究。
获得WPFToolkit.dll文件后,我们在项目中添加对它的引用,在XAML中因为WPFToolKit的命名空间不属于WPF和.NET Framework默认的映射命名空间内的一部分,所以需要在XAML文件中首先声明对其命名空间的引用,继而声明一个实例对象。为了简化我们的程序,这里我们将让Grid控件自己根据数据源生成列:AutoGenerateColumns=”True”.
<Window x:Class="DataGridIntro.SimpleDataGrid" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WpfToolkit" Loaded="Window_Loaded" Title="Simple DataGrid" Height="600" Width="800"> <Grid> <dg:DataGrid x:Name="NorthwindDataGrid" AutoGenerateColumns="True" /> </Grid> </Window> |
和普通WinForm控件一样,接下来需要对其数据源赋予数据。不同的是和所有WPF数据控件相同,这里的数据源属性为ItemSource.之前我们声明过Window的Loaded事件,直接在其中赋值即可:
private void Window_Loaded(object sender, RoutedEventArgs e) { using (NorthwindDataContext dc = new NorthwindDataContext()) { NorthwindDataGrid.ItemsSource = dc.Customers.ToList(); } } |
注意示例中使用了LINQ来获取Northwind数据库中Customers表的数据并赋予DataGrid作为数据源。和在ASP.NET/WinForm中相同,将List<>数据直接赋予ItemSource属性即可。这就是你需要做的所有一切,简单吧?看看您的成果.
哦,真的很Nice,因为我们仅仅只写了2行代码而已,就完成了这么些功能,太美妙了。迫不及待想试试了吧?快来吧。
2. DataGrid Styling
你肯定不想让你的数据展现给人一个过于朴素的感觉吧?让你的Grid变得更漂亮些,这是你必须做的,因为你在用WPF,你不能让客户觉得你是用普通的.NET程序去糊弄他的。当然,提供给客户一个焕然一新而美妙的User eXperience不是一个程序员就能做好的,那需要Designer的相当贡献。这里,只是提供个思路给大家去改变DataGrid的默认面貌。
很多时候我们对于每条记录都有一个主键去标明,而在业务逻辑中我们也用这些键值去标识一个事物。为了让这些更生动的表现出来,可以考虑加亮或者以特殊的方式显示出来。对于Customer来说,CustomerID就是能唯一标识这个客户的键值,我们为了更方便,将其以不同的风格显示。因为在WPF中Binding是我们最喜欢去用的东西之一,特别是你在需要处理与数据更新有关的东西时,它会帮助你很多。我们将某个列的是否主键(唯一标识对象)设为一个属性IsPrimaryKey,然后利用DataTrigger(因为是.NET Property)就可以动态更新资源。对于每一列,你都可以通过这个属性去判断是否需要去以特定风格显示。
DataGrid中的每一列是一个DataGridColumn对象,而每个DataGridColumn对应到最小又是一个DataGridCell对象。IsPrimaryKey是需要和列对其的,而对于这列的风格的设置缺需要影响到每一个单元格。因为DataGridTextColum,或者DataGridComboBoxColumn等都不具有这些自定义属性,所以我们需要声明一个继承于这个特定列类型的类来实现我们的自定义属性:
public class CustomColumn : DataGridTextColumn { public bool IsPrimaryKey { get { return (bool)GetValue(IsPrimaryKeyProperty); } set { SetValue(IsPrimaryKeyProperty, value); } } public static readonly DependencyProperty IsPrimaryKeyProperty = DependencyProperty.Register("IsPrimaryKey", typeof(bool), typeof(DataGridColumn), new FrameworkPropertyMetadata(false, null, null)); } |
在给DataGrid绑定数据时你需要通知这些特定的列根据数据源中的某个属性也罢,还是代码判断也罢去设定IsPrimaryKey的值。在这里如果你不使用AutoGenerateColumns,那对于每个列你可以设定Binding,并添加Converter来给IsPrimaryKey赋值。这个很容易,这里我们介绍另外一种方法,在后台代码里赋值。因为使用AutoGenerateColumns,所以我们可以在AutoGeneratingColumn事件中来做这些事情:
private void DataGrid_Standard_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e) { CustomColumn customColumn = new CustomColumn(); customColumn.CanUserSort = e.Column.CanUserSort; customColumn.Header = e.Column.Header; customColumn.DataFieldBinding = (e.Column as DataGridBoundColumn).DataFieldBinding; DataGrid dg = sender as DataGrid; Table<Order> orderTable = dg.ItemsSource as Table<Order>; MetaModel metaModel = orderTable.Context.Mapping.MappingSource.GetModel(orderTable.Context.GetType()); MetaTable metaTable = metaModel.GetTable(typeof(Order)); foreach (MetaDataMember identityMember in metaTable.RowType.IdentityMembers) { if (identityMember.MappedName == e.Column.Header.ToString()) { customColumn.IsPrimaryKey = true; break; } } e.Column = customColumn; } |
这段代码去数据库中去查找当前列是否是Primary Key,如果是将生成一个自定义的Column类型(继承于DataTextColumn)并将这个实例赋予当前列的实例对象,也就是说如果是Primary key那当前列就是CustomColumn,就具有IsPrimaryKey属性。我们的目的是要给主键添加风格,下边就来给他们定义样式了。记住刚才说的列的样式的影响实际上每个单元格的样式组成的,你就不难理解下边的样式定义:
<Style x:Key="defaultCellStyle" TargetType="{x:Type dg:DataGridCell}"> <Style.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=(Column).(local:CustomColumn.IsPrimaryKey), Mode=OneWay}" Value="True"> <Setter Property="Background" Value="Tan" /> <Setter Property="Foreground" Value="Red" /> </DataTrigger> </Style.Triggers> </Style> |
运行你的程序看看有没有效果呢?是不是看上去好些了?但这不是全部,你仍然需要更好的去为这些列做你自己的样式修改。或许,你可以自己试试值转换器(在前边的文章中讲过),两者配合会更好些。
DataGrid提供数据编辑,添加数据,删除数据这些功能,其实这和DataGrid的工作方式是分不开的。DataGrid在背后是由IEditableCollectionView来支持的,而ListCollectionView 和 BindingListCollectionView两个类实现了对这个接口的支持。简单来说,ListCollectionView是为ItemsControl的数据源创建视图,你可以在视图上来应用Group,Sort, Update等更高级的功能。BindingListCollectionView提供了为ADO.NET DataView对ItemsControl生成表现视图的方法。在编辑某行某列时,对表格中数据的特殊标注会很有效。这是根据IsEditing依赖属性来做触发器源并产生相应样式变更通知的:
<Style x:Key="defaultCellStyle" TargetType="{x:Type dg:DataGridCell}"> <Style.Triggers> <Trigger Property="IsEditing" Value="True"> <Setter Property="Foreground" Value="Red" /> <Setter Property="FontSize" Value="14" /> <Setter Property="FontWeight" Value="Bold" /> </Trigger> <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=(Column).(local:CustomColumn.IsPrimaryKey), Mode=OneWay}" Value="True"> <Setter Property="Background" Value="Tan" /> <Setter Property="Foreground" Value="Red" /> </DataTrigger> </Style.Triggers> </Style> <Style x:Key="defaultRowStyle" TargetType="{x:Type dg:DataGridRow}"> <Style.Triggers> <Trigger Property="IsEditing" Value="True"> <Setter Property="BorderBrush" Value="Red" /> <Setter Property="BorderThickness" Value="2" /> </Trigger> </Style.Triggers> </Style> |
很清楚的你会知道你在编辑哪条纪录的哪个值:
3. 控制更多Data Grid的视觉表现
有时候让用户可以随意更改DataGrid的某些表现方式更具有亲和力,比如背景颜色,GridLine等等,而这一切因为绑定的缘故让你很省心。因为都是依赖属性,应用TwoWay绑定模式,WPF会自动搞定这一切,一段代码加入到XAML中试试它能做到什么?
<StackPanel HorizontalAlignment="Left"> <CheckBox IsChecked="{Binding ElementName=dgList, Path=IsEnabled}" Content="Enable/Disable DataGrid" /> <CheckBox IsChecked="{Binding Path=CanUserAddRows, ElementName=dgList}" Content="DataGrid.CanUserAddRows" Width="186.876666666667" Height="15.96" HorizontalAlignment="Left" VerticalAlignment="Center" /> <CheckBox IsChecked="{Binding Path=CanUserDeleteRows, ElementName=dgList}" Content="DataGrid.CanUserDeleteRows" Width="186.876666666667" Height="15.96" HorizontalAlignment="Left" VerticalAlignment="Center" /> <CheckBox IsChecked="{Binding Path=CanUserResizeColumns, ElementName=dgList}" Content="DataGrid.CanUserResizeColumns" Width="190.32" Height="15.96" HorizontalAlignment="Left" VerticalAlignment="Center" /> <CheckBox IsChecked="{Binding Path=CanUserReorderColumns, ElementName=dgList}" Content="DataGrid.CanUserReorderColumns" Width="199.336666666667" Height="15.96" HorizontalAlignment="Left" VerticalAlignment="Center" /> <CheckBox IsChecked="{Binding Path=CanUserSortColumns, ElementName=dgList}" Content="DataGrid.CanUserSortColumns" Width="199.336666666667" Height="15.96" HorizontalAlignment="Left" VerticalAlignment="Center" /> <GroupBox Header="ColumnHeaderHeight" HorizontalAlignment="Left" VerticalAlignment="Center"> <TextBox Text="{Binding Path=ColumnHeaderHeight, ElementName=dgList}"/> </GroupBox> <GroupBox Header="RowHeaderWidth" HorizontalAlignment="Left" VerticalAlignment="Center"> <TextBox Text="{Binding Path=RowHeaderWidth, ElementName=dgList}"/> </GroupBox> <GroupBox Header="GridLine Options:" HorizontalAlignment="Left" VerticalAlignment="Center"> <ComboBox x:Name="GridLineVisibilityComboBox" SelectedItem="{Binding Path=GridLinesVisibility, ElementName=dgList}" HorizontalAlignment="Stretch" VerticalAlignment="Center"> <dg:DataGridGridLinesVisibility>All</dg:DataGridGridLinesVisibility> <dg:DataGridGridLinesVisibility>Horizontal</dg:DataGridGridLinesVisibility> <dg:DataGridGridLinesVisibility>None</dg:DataGridGridLinesVisibility> <dg:DataGridGridLinesVisibility>Vertical</dg:DataGridGridLinesVisibility> </ComboBox> </GroupBox> </StackPanel> |
也许你已经看出来了,改变DataGrid的各种属性及可视样式。没错,就是这么简单。Binding帮我们搞定了一切,而这都是你的杰作。Nice:)
4. Column的表现及DataGridComboBoxColumn
想过给列定义某些附加操作吗?比如让各个列可以随意调换位置,改变其BackGround,或者前景色?或者。。。。看看下边这个示例吧,它能帮到你什么吗?
在CustomColumn中声明IsHighLighted属性:
public bool IsHighlighted { get { return (bool)GetValue(IsHighlightedProperty); } set { SetValue(IsHighlightedProperty, value);} } public static readonly DependencyProperty IsHighlightedProperty = DependencyProperty.Register("IsHighlighted", typeof(bool), typeof(DataGridColumn), new FrameworkPropertyMetadata(true, null, null)); |
在XAML中声明CustomColum列并定义Header模板和绑定:
<local:CustomColumn Width="150" CanUserSort="True" Header="Custom Column" DataFieldBinding="{Binding Path=FirstName}"> <local:CustomColumn.CellStyle> <Style TargetType="{x:Type dg:DataGridCell}" BasedOn="{StaticResource defaultCellStyle}"> <Style.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=(Column).(local:CustomColumn.IsHighlighted), Mode=OneWay}" Value="True"> <Setter Property="Background" Value="Tan" /> <Setter Property="Foreground" Value="Red" /> </DataTrigger> </Style.Triggers> </Style> </local:CustomColumn.CellStyle> <local:CustomColumn.HeaderTemplate> <DataTemplate> <StackPanel> <CheckBox Name="chBox_Highlight" IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridColumnHeader}}, Path=(Column).(local:CustomColumn.IsHighlighted), Mode=OneWayToSource}" Content="Highlight Cells (special)"/> <TextBlock Text="{Binding}" /> </StackPanel> </DataTemplate> </local:CustomColumn.HeaderTemplate> </local:CustomColumn> |
看到结果了吧?很美妙,但过程很简单,对吧?你可以用Converter来帮助你一起做些事情,而且对于一些诸如IsFrozen等这些Column自带属性你并不用自定义来实现,不是更简单吗?
DataGridComboBoxColumn它总是能够帮助我们做很多事情,直接上代码吧,你发现它简直太容易了。
<dg:DataGridComboBoxColumn Width="120" Header="Cake" DataFieldBinding="{Binding Path=Cake}"> <dg:DataGridComboBoxColumn.ItemsSource> <collection:ArrayList> <sys:String>Chocolate</sys:String> <sys:String>Vanilla</sys:String> </collection:ArrayList> </dg:DataGridComboBoxColumn.ItemsSource> </dg:DataGridComboBoxColumn> |
对于编辑时产生一个可选列表,你也可以通过定义编辑模板来产生,就像在ASP.NET中的GridView的TemplateColumn一样。这样在很多时候可能更有用,因为对编辑模板中的ComboBox控件来说你可以通过Binding给ItemSource来绑定数据,这样更符合你的应用场景。
<dg:DataGridTemplateColumn Width="150" Header="Picture"> <dg:DataGridTemplateColumn.CellTemplate> <DataTemplate> <Image Source="{Binding Path=Picture}" Height="35" Width="35" /> </DataTemplate> </dg:DataGridTemplateColumn.CellTemplate> <dg:DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <ComboBox SelectedItem="{Binding Path=Picture, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> <ComboBox.ItemsSource> <collection:ArrayList> <sys:String>Images\02.jpg</sys:String> <sys:String>Images\03.JPG</sys:String> <sys:String>Images\04.jpg</sys:String> <sys:String>Images\05.jpg</sys:String> <sys:String>Images\06.jpg</sys:String> </collection:ArrayList> </ComboBox.ItemsSource> </ComboBox> </DataTemplate> </dg:DataGridTemplateColumn.CellEditingTemplate> </dg:DataGridTemplateColumn> |
仅仅是DataGird的初步介绍,获取示例代码来看个究竟吧:)