在WPF应用程序中,DataGrid控件是一个功能强大的数据展示和编辑工具。然而,正确地设置DataGrid的绑定和格式化往往令人困惑,尤其是对于初学者。本文将详细讲解如何绑定DataGrid到业务逻辑数据,并对其进行格式化,以提高数据的可读性和用户体验。
首先,需要了解DataGrid的容器层次结构。一个DataGrid包含多个DataGridRow,每个DataGridRow又包含多个DataGridCell。在默认的文本列中,每个DataGridCell包含一个TextBlock。在编辑模式下,TextBlock会被TextBox替代。尽管如此,DataGrid的可视化树结构比这更为复杂。值得注意的是,DataGridColumn并不属于可视化树的一部分,但它定义的属性会被应用到该列的所有单元格上。
在WPF中,绑定是将UI元素的属性与数据源中的属性关联起来的过程。绑定的目标是FrameworkElement的属性。为了使绑定生效,WPF需要两个源信息:
通常,源(Source)是从父容器的DataContext继承而来,这个父容器通常是Window。但是,DataGrid的DataContext不能用于行和单元格的绑定,因为每一行都需要绑定到不同的业务逻辑对象。
以库存数据为例,可以定义一个StockItem类来表示库存项:
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 |
连接DataGrid与业务数据并不简单。通常,会使用CollectionViewSource来实现这一连接:
<Window.Resources>
<CollectionViewSource x:Key="ItemCollectionViewSource" CollectionViewType="ListCollectionView" />
</Window.Resources>
在XAML中定义CollectionViewSource,并在后台代码中将其与业务数据关联:
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 });
// ...
CollectionViewSource itemCollectionViewSource;
itemCollectionViewSource = (CollectionViewSource)FindResource("ItemCollectionViewSource");
itemCollectionViewSource.Source = itemList;
注意,必须设置CollectionViewType。如果不设置,GridView将使用BindingListCollectionView,这不支持排序。
格式化列很简单,只需在DataGridColumn中设置属性即可。例如,设置字体加粗:
<DataGridTextColumn Binding="{Binding Path=Name}" Header="Name" FontWeight="Bold" />
绑定在这里并不涉及格式化,而是指定单元格的内容(即TextBlock的Text属性)。
格式化整行比较特殊,因为会有多行。DataGrid为此提供了RowStyle属性。这个样式将应用于每个DataGridRow:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Item.Quantity, Converter={StaticResource QuantityToBackgroundConverter}}" />
</Style>
</DataGrid.RowStyle>
这里的关键是,DataGridRow有一个Item属性,它包含了该行的业务逻辑对象。因此,绑定必须绑定到它自身!路径有点令人惊讶,因为Item是Object类型,它不知道任何业务数据属性。但是,WPF绑定会应用一些魔法,找到StockItem的Quantity属性。
仅格式化单元格而不是整行是一个挑战。在文本列中,单元格有一个TextBlock,需要对其进行样式设置。创建TextBlock的Style很容易,但是如何将TextBlock属性绑定到正确的业务对象呢?DataGrid已经绑定了TextBlock的Text属性。如果样式仅依赖于单元格值,可以使用自绑定到这个Text属性:
<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource Self}, Path=Text, Converter={StaticResource QuantityToForegroundConverter}}" />
例如,在库存网格中,Quantity应该始终大于或等于零。如果数量为负数,则表示错误,应该以红色显示。
最复杂的情况是,如果单元格格式不依赖于单元格值,而是依赖于其他业务数据。例如,如果物品已过时,其数量应该显示为删除线。为了实现这一点,需要将TextDecorations属性链接到该行的业务对象。这意味着TextBlock需要找到父DataGridRow。幸运的是,可以使用相对源绑定到父视觉对象:
<Window.Resources>
<Style x:Key="QuantityStyle" TargetType="TextBlock">
<Setter Property="TextDecorations" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}, Path=Item.IsObsolete, Converter={StaticResource IsObsoleteToTextDecorationsConverter}}" />
</Style>
</Window.Resources>
...
<DataGrid ...>
...
<DataGrid.Columns>
...
<DataGridTextColumn Binding="{Binding Path=Quantity}" Header="Quantity" ElementStyle="{StaticResource QuantityStyle}" />
...
</DataGrid.Columns>
</DataGrid>
ElementStyle与CellStyle的区别在于,DataGridTextColumn继承自DataGridBoundColumn,后者又继承自DataGridColumn。从DataGridBoundColumn继承了ElementStyle属性,这个样式应用于TextBlock控件。从DataGridColumn继承了CellStyle属性,这个样式应用于DataGridCell控件。