65.9K
CodeProject 正在变化。 阅读更多。
Home

使用绑定进行 WPF DataGrid 格式化的指南

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (41投票s)

2013年11月24日

CPOL

6分钟阅读

viewsIcon

234120

downloadIcon

7436

使用 Style 和 Binding 来控制 WPF DataGridCell 的外观

引言

根据业务逻辑数据对WPF DataGrid 内容进行格式化非常困难,尤其是因为MSDN对此几乎没有任何说明。我花了几个星期才弄清楚如何正确设置绑定。让我向您展示如何做到这一点,以节省您的时间和无休止的网络搜索。

WPF DataGrid 结构

DataGrid 的容器层次结构如下所示

DataGrid 
  DataGridRows
     DataGridCell
       TextBlock 

一个 DataGrid 包含 DataGridRowDataGridRow 包含 DataGridCell,如果它是 TextColumn 且处于读取模式(编辑模式使用 TextBox),则 DataGridCell 包含一个 TextBlock。当然,视觉树会更复杂一些。

请注意,DataGridColumn 不是视觉树的一部分。在 DataGridColumn 中定义的任何内容都将应用于该列的所有单元格。

WPF 绑定基础

绑定被分配给一个 FrameworkElement 属性,该属性构成了绑定的目标。

WPF 需要两个源信息才能使绑定工作

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

通常,Source 从父容器的 DataContext 继承,通常是窗口本身。但是,DataGridDataContext 不能用于行和单元格的绑定,因为每行都需要绑定到不同的业务逻辑对象。

DataGridColumn 使用 DataGridColumn.Binding 属性指定要在单元格中显示的绑定的值。DataGrid 在运行时为每个 TextBlock.Text 创建一个绑定。不幸的是,DataGrid 不支持绑定 TextBlock 的任何其他属性。如果您尝试自己设置 TextBlock 的样式,绑定很可能会失败,因为它不知道应使用 ItemsSource 中的哪个业务对象。

使用的业务数据

业务数据示例基于一些库存盘点数据。一个库存项看起来像这样

public class StockItem {
  public string Name { get; set; }
  public int Quantity { get; set; }
  public bool IsObsolete { get; set; }
}

示例数据

名称 数量 IsObsolete (是否已过时)
许多商品 100 false
足够多的商品 10 false
缺货商品 1 false
有错误的项目 -1 false
已过时商品 200 true

连接 DataGrid 与业务数据

即使是连接 DataGrid 与业务数据也并非易事。基本上,CollectionViewSource 用于将 DataGrid 与业务数据连接起来。

CollectionViewSource 负责实际的数据导航、排序、过滤等。

<Window.Resources>
    <CollectionViewSource x:Key="ItemCollectionViewSource"  CollectionViewType="ListCollectionView"/>
</Window.Resources>   
<DataGrid
  DataContext="{StaticResource ItemCollectionViewSource}"
  ItemsSource="{Binding}"
  AutoGenerateColumns="False"
  CanUserAddRows="False">  
//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; 
  1. Windows.Resource 中定义 CollectionViewSource
  2. 这里要注意的是,您 **必须** 设置 CollectionViewType。如果不行,GridView 将使用 BindingListCollectionView,它不支持排序。当然,MSDN 在任何地方都没有解释这一点。
  3. DataGridDataContext 设置为 CollectionViewSource
  4. 在代码隐藏中,找到 CollectionViewSource 并将您的业务数据分配给 Source 属性。

在本文中,数据仅被读取。如果用户应该能够编辑数据,请使用 ObservableCollection

DataGrid格式化

格式化列

格式化整列很容易。只需在 DataGridColumn 中直接设置属性,例如 Fontweight

<DataGridTextColumn Binding="{Binding Path=Name}" Header="Name" FontWeight="Bold"/>

这里的绑定不涉及格式化,而是指定单元格的内容(即 TextBlockText 属性)。

格式化整行

格式化行比较特殊,因为会有很多行。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>

DatGridRow 有一个 Item 属性,其中包含该行的业务逻辑对象。因此,DataRow 的绑定必须绑定到自身!路径有点令人惊讶,因为 Item 的类型是 Object,并且不知道任何业务数据属性。但是 WPF 绑定会应用一些魔力,并找到 StockItemQuantity 属性。

在此示例中,行的背景取决于业务对象的 Quantity 属性的值。如果库存商品很多,背景应为白色;如果仅剩少量,背景应为灰色。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();
   }
}  

注意

  1. 只能在 DataGridRow 中设置某些属性,例如 Background 颜色。其他属性,如 Fonts,必须在 DataGridCell 中设置,即 TextBlock
  2. DataGridCellTextBlock 等在 DataGridRow 上绘制。如果两者都设置了不同的 Background,则 DataGridRowBackground 将被隐藏。

基于显示值格式化单元格

仅格式化单元格而不是整行是一个挑战。在文本列中,单元格有一个需要设置样式的 TextBlock。创建 TextBlockStyle 很容易,但 TextBlock 属性如何绑定到正确的业务对象?DataGrid 已经在绑定 TextBlockText 属性。如果样式仅取决于单元格的值,我们可以简单地使用对该 Text 属性的自绑定。

示例:在我们的库存网格中,Quantity 应始终大于或等于零。如果数量为负,则表示错误,应显示为红色。

<Setter Property="Foreground" 
  Value="{Binding 
    RelativeSource={RelativeSource Self}, 
    Path=Text, 
    Converter={StaticResource QuantityToForegroundConverter}}" /> 

基于业务逻辑数据格式化单元格

最复杂的情况是,如果单元格格式不取决于单元格值,而是取决于其他业务数据。在我们的示例中,如果商品已过时,其数量应显示为删除线。为实现此目的,需要将 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,而 DataGridBoundColumn 又继承自 DataGridColumn

DataGridBoundColumn,它继承了 ElementStyle 属性。此样式应用于 TextBlock 控件。

DataGridColumn,它继承了 Cell 属性。此样式应用于 DataGridCell 控件。

请注意,在上面的视觉树中,DataGridCell 包含一个 TextBlockDataGridCell 首先被绘制,然后 TextBlock 在其之上绘制。

由于 TextBlockDataGridCell 都继承自 Control,因此它们都具有用于背景、边框、字体、前景、填充和内容对齐的属性。这意味着可以在 ElementStyleCellStyle 中设置 Background 颜色。

何时使用哪一个?我们在这里使用 ElementStyle,因为它还支持设置 TextBlock 特定的属性,例如 TextDecorations。对于某些属性,如 Background,应使用 CellStyle,因为它覆盖了整个单元格,而 ElementStyle 只设置了 TextBlock 的背景,只覆盖了单元格的一部分。

也可以同时设置两种样式。如果它们使用不同的值设置相同的属性,则 ElementStyle 获胜。

延伸阅读

如果您阅读到这里,说明您对 WPF DataGrid 确实很感兴趣。在这种情况下,我想向您推荐我写的另一篇关于 WPF DataGrid 的文章。

WPF DataGrid:解决排序、滚动到视图、刷新和焦点问题

© . All rights reserved.