将基于WPF框架的数据表格内的业务逻辑数据格式化是非常复杂的过程,尤其是当MSDN没有提供提供任何帮助的时候。我花了数周的时间弄清了如何正确绑定数据,下面我的讲解将会帮助你终止无尽的搜索和浪费时间。
作为容器的数据表格层次差不多就是下面这样了:
数据表(DataGrid ) 行(DataGridRows) 单元格(DataGridCell) 文本块(TextBlock )
public class StockItem { public string Name { get; set; } public int Quantity { get; set; } public bool IsObsolete { get; set; } }
例:
Name | Quantity | IsObsolete |
Many items | 100 | false |
Enough items | 10 | false |
Shortage item | 1 | false |
Item with error | -1 | false |
Obsolete item | 200 | true |
难点并不在于数据表和业务数据之间连接的建立,通常我们可以用CollectionViewSource来建立此种连接。
CollectionViewSource完成导航、排序、筛选等事项。
1) 在Windows.Resource中定义CollectionViewSource
<Window.Resources> <CollectionViewSource x:Key="ItemCollectionViewSource" CollectionViewType="ListCollectionView"/> </Window.Resources>
关键在于,这里你必须使用CollectionViewType。否则数据表将使用 BindingListCollectionView,而这个空间将不具备排序功能。当然,MSDN并没有在任何地方提及这个事情。
2) 设置数据表的DataContext属性和 CollectionViewSource建立连接关系。
<DataGrid DataContext="{StaticResource ItemCollectionViewSource}" ItemsSource="{Binding}" AutoGenerateColumns="False" CanUserAddRows="False">3) 在下面的代码中,找到CollectionViewSource,并将业务数据指定为来源属性。
//create business data var itemList = new List<stockitem>(); itemList.Add(new StockItem {Name= "Many items", Quantity=100, IsObsolete=false}); itemList.Add(new StockItem {Name= "Enough items", Quantity=10, IsObsolete=false}); ... //link business data to CollectionViewSource CollectionViewSource itemCollectionViewSource; itemCollectionViewSource = (CollectionViewSource)(FindResource("ItemCollectionViewSource")); itemCollectionViewSource.Source = itemList;
<DataGridTextColumn Binding="{Binding Path=Name}" Header="Name" FontWeight="Bold"/>目前为止的数据捆绑还未涉及到表格的创建,仅是对内容进行编辑(例如文本块中的文本属性)。
构建行的方法比较特殊,因为涉及到很多行,所以数据表提供了RowStyle属性。对此属性进行操作将会涉及到所有的行。
<datagrid.rowstyle> <style targettype="DataGridRow"> <Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Item.Quantity, Converter={StaticResource QuantityToBackgroundConverter}}"/> </style> </datagrid.rowstyle>
DatGridRow 有一个 包含该行的业务逻辑对象的 Item 属性,因此 DataRow 的绑定必须绑定到它自己!有点奇怪的是 Path,因为 Item 是类型对象并且不知道任何的业务数据属性。但 WPF 绑定应用了一点魔力,无论怎样都能查找 Stock 项目的 Quantity 属性。
在这个例子中,背景(background)这一行依赖于业务对象的 Quantity 属性的值。在 stock 中如果有许多项,背景应该是白色的,如果只剩下几个,背景应该是灰色的。QuantityToBackgroundConverter 完成了必要的计算:
class QuantityToBackgroundConverter: IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is int) { int quantity = (int)value; if (quantity>=100) return Brushes.White; if (quantity>=10) return Brushes.WhiteSmoke; if (quantity>=0) return Brushes.LightGray; return Brushes.White; //quantity should not be below 0 } //value is not an integer. Do not throw an exception in the converter, but return something that is obviously wrong return Brushes.Yellow; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
仅格式化一个单元格而不是整行是一个挑战。在文本列中,单元格有一个需要样式的 TextBlock。为 TextBlock 创建一个样式是简单的,但如何将 TextBlock 属性限制在适当的业务对象上呢?DataGrit 已经绑定到 TextBlock 的 Text 属性了。如果样式仅依赖与单元格的值,我们可以简单地对这个 Text 属性使用一个自我约束 。
例如:在我们的 stock 网格中,Quantity 应该一直大于或等于零。如果某个 quantity 是不符的,那么它将以红色显示一个错误:
<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource Self}, Path=Text, Converter={StaticResource QuantityToForegroundConverter}}" />
最复杂的情况是如果单元格格式不是依赖于单元格的值,而是其他的一些业务数据。在我们的例子中,Quantity 中的某一项如果是丢弃的就应该显示为完全穿透。 为了达到这个目的,TextDecorations 属性需要链接到那一行的业务对象。这意味着 TextBlock 不得不查找父级的 DataGridRow。幸运的是,可以使用一个相对源绑定到一个父级可视对象:
public class IsObsoleteToTextDecorationsConverter: IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is bool) { if ((bool)value) { TextDecorationCollection redStrikthroughTextDecoration = TextDecorations.Strikethrough.CloneCurrentValue(); redStrikthroughTextDecoration[0].Pen = new Pen {Brush=Brushes.Red, Thickness = 3 }; return redStrikthroughTextDecoration; } } return new TextDecorationCollection(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
请在 Zip 文件中查看样例的完整源代码。
代码(Zip 文件)