WPF DataGrid显格式

Guide to WPF DataGrid formatting using bindings

Peter Huber SG, 25 Nov 2013 CPOL
loading...
Rate:
Add a reason or comment to your vote: x
Votes of 3 or less require a comment
 
Using Style and Binding to control WPF DataGridCell appearance.

Introduction

Formatting WPF DataGrid content depending on business logic data is way too difficult. Especially since MSDN is not telling you anything about it. I have spent weeks to figure out how to get the binding right. Let me show you how it is done to save you time and endless searches on the Internet.  

WPF DataGrid  Structure  

The container hierarchy of a DataGrid looks like this: 

DataGrid 
  DataGridRows
     DataGridCell
       TextBlock   

A DataGrid contains DataGridRows which contain DataGridCells which contain exactly one TextBlock, if it is a TextColumn and in read mode (editing mode uses a TextBox). Of course, the visual tree is a bit more complicated:   

Note that DataGridColumn is not part of the visual tree. Whatever is defined in DataGridColumn will be applied to all cells of that column. 

WPF Binding Basics

The binding gets assigned to a FrameworkElement property, which constitutes the target of the binding.

WPF needs two source information to make a binding work:  

  • Source: Which object provides the information
  • Path: Which property of the source should be used 

Usually, the Source gets inherited from the DataContext of a parent container, often the Window itself. But DataGrid's DataContext cannot be used for the binding of rows and cells, because each row needs to bind to a different business logic object.

DataGridColumn specifies the binding for the value to be displayed in the cell with the DataGridColumn.Binding property. The DataGrid creates during runtime a binding for every TextBlock.Text. Unfortunately, the DataGrid does not support binding for any other property of TextBlock. If you try to setup a style for the TextBlock yourself, the binding will most likely fail, because it wouldn't know which business object from the ItemsSource to use.

Business data used

The business data example is based on some stock taking figures. A stock item looks like this:

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

The sample data: 

NameQuantity  IsObsolete  
Many items 100false
Enough items10false
Shortage item1false
Item with error  -1false
Obsolete item200true 

Connecting a DataGrid with business data

Even connecting a DataGrid with the business data is not trivial. Basically, a CollectionViewSource is used to connect the DataGrid with the business data: 

The CollectionViewSource does the actual data navigation, sorting, filtering, etc.  

    1. Define a CollectionViewSource in Windows.Resource 
<Window.Resources>
    <CollectionViewSource x:Key="ItemCollectionViewSource" CollectionViewType="ListCollectionView"/> </Window.Resources> 
    1. The gotcha here is that you
must
    1. set the
CollectionViewType
    1. . If you don't, the GridView will use
BindingListCollectionView
    1. , which does not support sorting. Of course, MSDN does not explain this anywhere.

 

    1. Set the DataContext of the DataGrid to the CollectionViewSource .
<DataGrid
  DataContext="{StaticResource ItemCollectionViewSource}"
  ItemsSource="{Binding}" AutoGenerateColumns="False" CanUserAddRows="False"> 
    1. In the code behind, find the CollectionViewSource and assign your business data to the Source property
//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;</stockitem> 

In this article, data gets only read. If the user should be able to edit the data, use an ObservableCollection

DataGrid Formatting 

Formatting a column

Formatting a whole column is easy. Just set the property, like Fontweight directly in the DataGridColumn:

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

The binding here is not involved with the formatting, but specifies the content of the cell (i.e., Text property of TextBlock).

Formatting complete rows

Formatting the rows is special, because there will be many rows. The DataGrid offers for this purpose the RowStyle property. This style will be applied to every DataGridRow.

<datagrid.rowstyle>
  <style targettype="DataGridRow"> <Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Item.Quantity, Converter={StaticResource QuantityToBackgroundConverter}}"/> </style> </datagrid.rowstyle>

The DatGridRow has an Item property, which contains the business logic object for that row. The binding for the DataRow must therefore bind to itself ! The path is a bit surprising, because Item is of type Object and doesn't know any business data properties. But the WPF binding applies a bit of magic and finds the Quantity property of StockItem anyway.

In this example, the background of a row depends on the value of the Quantity property of the business object. If there are many items in stock, the background should be white, if only few are left, the background should be grey. The QuantityToBackgroundConverter performs the necessary calculation:

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(); } } 

Formatting a cell based on the displayed value

Formatting just a cell instead of the whole row is a challenge. In a text column, the cell has a TextBlock which needs to be styled. To create a Style for TextBlocks is easy, but how can the TextBlock property be bound to the proper business object ? The DataGrid is binding already the Text property of the TextBlock. If the styling depends only on the cell value, we can simply use a self binding to this Text property.

Example: In our stock grid, the Quantity should always be greater than or equal zero. If a quantity is negative, it is an error and should be displayed in red: 

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

Formatting a cell based on business logic data

The most complex case is if the cell format does not depend on the cell value, but some other business data. In our example, the quantity of an item should be displayed as strike through if it is obsolete. To achieve this, the TextDecorations property needs to be linked to the business object of that row. Meaning the TextBlock has to find the parent DataGridRow. Luckily, binding to a parent visual object can be done with a relative source:

<Setter Property="TextDecorations" 
  Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}, Path =Item.IsObsolete, Converter={StaticResource IsObsoleteToTextDecorationsConverter}}" />
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(); } }

Code

See the Zip file for the complete source code of the sample.

&amp;lt;a href="http://ad.doubleclick.net/N6839/jump/lqm.codeproject.site/Platforms-Frameworks-Libraries/Windows-Presentation-Foundation/Controls;sz=300x250;ord=635503472155835127?"&amp;gt;&amp;lt;img src="http://ad.doubleclick.net/N6839/ad/lqm.codeproject.site/Platforms-Frameworks-Libraries/Windows-Presentation-Foundation/Controls;sz=300x250;ord=635503472155835127?" width="300px" height="250px" /&amp;gt;&amp;lt;/a&amp;gt;

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

转载于:https://www.cnblogs.com/qq247039968/p/4066073.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值