WPF DataGrid 使用绑定来对数据进行格式化

简介

将基于WPF框架的数据表格内的业务逻辑数据格式化是非常复杂的过程,尤其是当MSDN没有提供提供任何帮助的时候。我花了数周的时间弄清了如何正确绑定数据,下面我的讲解将会帮助你终止无尽的搜索和浪费时间。

WPF 数据表格结构 

作为容器的数据表格层次差不多就是下面这样了:

数据表(DataGrid )
  行(DataGridRows)
     单元格(DataGridCell)
       文本块(TextBlock  )

一个数据表包含多个数据行,一个数据行包含多个单元格,单元格能且仅能包含一个文本块,前提是文本块是一个列文本(TextColumn)并且在只读模式下(在编辑模式下会变为文本框(TextBox))。在树形结构下,表的结构有点复杂:  

需要注意的是,数据列不是树形结构的一部分。但是对数据列做出的任何定义将会被应用至那一列的所有单元格。

WPF 捆绑基础

捆绑将会被分配至一个FrameworkElement属性,由这个属性组成要捆绑的目标容器。

WPF捆绑需要2个参数:  

来源: 哪个对象提供了信息?

路径: 对象下的哪个属性可以使用 ?

通常情况下,来源继承自它自己父容器的数据文本(DataContext)属性。但是数据表格的此属性无法被用于捆绑行和单元格,因为每一行都需要捆绑不同的业务逻辑对象。

数据列将指定的捆绑数据呈现在每个单元格中,与对应的属性捆绑。在运行时,数据表会为每一个文本块的文本(TextBlock.Text)创建一个捆绑。不幸的是,并不是所有属性的文本块都支持被数据表捆绑。如果你想自定义文本块的格式,那么你很有可能会失败,因为捆绑操作并不知道你使用的是目标资源中的哪个业务对象。

提取业务数据

通过下面的方法获取数据:

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来建立此种连接。 

WPF DataGrid 使用绑定来对数据进行格式化_第1张图片

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; 

本文中的数据权限仅为可读。如果想让用户可以修改数据,可以使用ObservableCollection。

构建数据表 

构建列

WPF DataGrid 使用绑定来对数据进行格式化_第2张图片

如果仅是对整个列进行调整,只需设置数据列(DataGridColumn)中的某个属性就好了,比如字体加粗:

<DataGridTextColumn Binding="{Binding Path=Name}" Header="Name" FontWeight="Bold"/>
目前为止的数据捆绑还未涉及到表格的创建,仅是对内容进行编辑(例如文本块中的文本属性)。 

构建所有的行

WPF DataGrid 使用绑定来对数据进行格式化_第3张图片

构建行的方法比较特殊,因为涉及到很多行,所以数据表提供了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();
   }
}  

格式化一个基于所显示的值的单元格

WPF DataGrid 使用绑定来对数据进行格式化_第4张图片

仅格式化一个单元格而不是整行是一个挑战。在文本列中,单元格有一个需要样式的 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 文件) 











你可能感兴趣的:(数据,datagrid,格式化,WPF,Something)