在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\2.jpg</sys:String>
<sys:String>Images\3.JPG</sys:String>
<sys:String>Images\4.jpg</sys:String>
<sys:String>Images\5.jpg</sys:String>
<sys:String>Images\6.jpg</sys:String>
</collection:ArrayList>
</ComboBox.ItemsSource>
</ComboBox>
</DataTemplate>
</dg:DataGridTemplateColumn.CellEditingTemplate>
</dg:DataGridTemplateColumn>
僅僅是DataGird的初步介紹
本文配套源碼