WPF DataGrid绑定和格式化详解

WPF应用程序中,DataGrid控件是一个功能强大的数据展示和编辑工具。然而,正确地设置DataGrid的绑定和格式化往往令人困惑,尤其是对于初学者。本文将详细讲解如何绑定DataGrid到业务逻辑数据,并对其进行格式化,以提高数据的可读性和用户体验。

WPFDataGrid结构

首先,需要了解DataGrid的容器层次结构。一个DataGrid包含多个DataGridRow,每个DataGridRow又包含多个DataGridCell。在默认的文本列中,每个DataGridCell包含一个TextBlock。在编辑模式下,TextBlock会被TextBox替代。尽管如此,DataGrid的可视化树结构比这更为复杂。值得注意的是,DataGridColumn并不属于可视化树的一部分,但它定义的属性会被应用到该列的所有单元格上。

WPF绑定基础

在WPF中,绑定是将UI元素的属性与数据源中的属性关联起来的过程。绑定的目标是FrameworkElement的属性。为了使绑定生效,WPF需要两个源信息:

  • 源(Source):提供信息的对象。
  • 路径(Path):源对象中应该使用的属性。

通常,源(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与业务数据连接

连接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,这不支持排序。

DataGrid格式化

格式化列很简单,只需在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控件。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485