WPF DataGrid: Stock and Template Columns
Overview
The DataGrid uses a set of DataGridColumns to describe how to display its data just like a GridView has a set of GridViewColumns to describe its data. In my first post, the sample I used auto-generated the columns for you but this time I want to go over how to create the columns manually.
Column Types
Currently in our WPF DataGrid we have four available stock columns and a template column:
· DataGridTextColumn
· DataGridCheckBoxColumn
· DataGridComboBoxColumn
· DataGridHyperlinkColumn
· DataGridTemplateColumn
Each column type corresponds to the UIElement that it will show in each of the cells in that column. The DataGridTemplateColumn allows you to customize the template with a custom UIElement or tree of Elements for each of the cells in the column. Template columns derive from DataGridColumn, whereas stock columns derive from DataGridBoundColumn which derives from DataGridColumn.
Why the difference in inherited classes? The main difference is that a DataGridBoundColumn includes the property Binding, which is a convenience property for mapping a data item property in the ItemsSource to its corresponding set of cells in the column. Here is an example:
<dg:DataGridTextColumn Binding="{Binding Path=FirstName}" />
The concept is basically the same as setting the DisplayMemberBinding property on a GridViewColumn of a GridView. I also talked about this in my post, Dissecting the Visual Layout. For a DataGridTemplateColumn, since you are free to set the template to whatever you like, we cannot automatically figure out where you would like to place the binding. Because of that, you are required to setup the binding yourself and therefore the class does not derive from DataGridBoundColumn.
Stock Columns and styling
For stock columns or DataGridBoundColumns, the main properties you will likely use are the Binding property, which I describe above, and the Header property. Header is just like GridViewColumn.Header, which displays the text on the DataGridColumnHeader of that column.
<dg:DataGridTextColumn Header="First Name" Binding="{Binding Path=FirstName}" />
Styling a stock column can be accomplished with these particular properties:
· CellStyle
· EditingElementStyle
· ElementStyle
Recall in my post, Dissecting the Visual Layout, when the DataGridCell is created its Content property is set after asking DataGridColumn to generate the visual tree. Well, depending if you are in an editing state or not, the column will generate a different UIElement for each state. For a DataGridTextColumn in an editing state, a TextBox is created. In a non-editing state, a TextBlock is created. For a DataGridComboBoxColumn in an editing state, a ComboBox is created, while in a non-editing state, a TextBlock is created.
With that in mind, CellStyle is the style for the overall DataGridCell (which is a ContentControl and items container), EditingElementStyle is the style for the DataGridCell’s Content generated during the editing state and ElementStyle is the style for the DataGridCell’s Content generated during the non-editing state. Here is an example:
<dg:DataGridTextColumn Header="First Name" Binding="{Binding Path=FirstName}">
<dg:DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type dg:DataGridCell}">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</dg:DataGridTextColumn.CellStyle>
<dg:DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="Red" />
</Style>
</dg:DataGridTextColumn.EditingElementStyle>
<dg:DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="LightBlue" />
</Style>
</dg:DataGridTextColumn.ElementStyle>
</dg:DataGridTextColumn>
You should note that the Bold FontWeight will be applied to both the editing and non-editing element while the Foreground property set in CellStyle will be overwritten by the Foreground property set by each of the element styles.
Template Columns
For DataGridTemplateColumns, the Header and CellStyle property still apply but there isn’t a Binding, EditingElementStyle, or ElementStyle property. Instead, you have the properties, CellTemplate and CellEditingTemplate. DataGridCell’s Content generation is still the same; it’s just that it uses these two cell templates to generate the content instead. Here is an example:
<dg:DataGridTemplateColumn Header="First Name">
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="{Binding Path=FirstName}" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
<dg:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellEditingTemplate>
</dg:DataGridTemplateColumn>
In a non-editing state a Button is created as the UIElement for the cell and is bound to the FirstName property of my data source. In an editing state a TextBox is created as the UIElement. Having an editable Button in a DataGrid may not be a common scenario but I did want to show both CellTemplate and CellEditingTemplate. If you do not want to make it editable, one thing you can do is just not declare the CellEditingTemplate. Notice in both templates I setup the binding myself since I do not have a Binding like DataGridBoundColumns.
Important: Also notice that in CellEditingTemplate I had to declare the binding with Mode set to TwoWay and UpdateSourceTrigger set to PropertyChanged. This is another consequence of not getting the binding utilities that DataGridBoundColumns provide. In a DataGridBoundColumn, when cells are edited and committed, the DataGrid will take care of updating the sources for you. In the case of the template column, you will have to update the source yourself. This issue is only for a commit scenario. The DataGrid will still be able to rollback on a cancel command.
Sizing Columns
Column sizing uses a special class called DataGridLength that has sizing properties specifically for a row and column scenario. The types of widths are as follows:
· Pixel
· SizeToCells
· SizeToHeader
· Auto
· Star
Pixel uses absolute sizing on the column width, SizeToCells sizes the column width to the largest cell, SizeToHeader sizes the column width to the header width, Auto sizes the column width to either the largest cell or the header width whichever is larger, and Star sizing follow the proportional sizing like a Grid panel. See the final example below for usage of the different widths.
Putting it all together
So with all that, I have updated the sample from my first post on DataGrid with what I discuss here. Instead of auto-generating the columns, I’ve explicitly declared each column and I’ve setup different widths for you to get an idea of how that works. You can also double-click on the column header grippers to change the width to Auto. This functionality is similar to Windows Explorer. I’ve also created a template column that displays an Image UIElement when in non-edit mode and a CombBox UIElement when in editing mode. Notice the differences in how I setup the data bindings between the template column versus the bound columns. I’ve also added a DataGridCell style and DataGridRow style that updates the BorderBrush and BorderThickness when in edit mode. You can download the sample here.
<Style x:Key="defaultCellStyle" TargetType="{x:Type dg:DataGridCell}">
<Style.Triggers>
<Trigger Property="IsEditing" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="2" />
</Trigger>
</Style.Triggers>
</Style>
<dg:DataGrid AutoGenerateColumns="False" CellStyle="{StaticResource defaultCellStyle}" …>
<dg:DataGrid.Columns>
<dg:DataGridTextColumn Width="130" Header="First Name" Binding="{Binding Path=FirstName}" />
<dg:DataGridTextColumn Width="Auto" Header="Last Name" Binding="{Binding Path=LastName}" />
<dg:DataGridCheckBoxColumn Width="SizeToCells" Header="Likes Cake" Binding="{Binding Path=LikesCake}" />
<dg:DataGridComboBoxColumn Width="200" Header="Cake" SelectedItemBinding="{Binding Path=Cake}">
<dg:DataGridComboBoxColumn.ItemsSource>
<col:ArrayList>
<sys:String>Chocolate</sys:String>
<sys:String>Vanilla</sys:String>
</col:ArrayList>
</dg:DataGridComboBoxColumn.ItemsSource>
</dg:DataGridComboBoxColumn>
<dg:DataGridHyperlinkColumn Width="SizeToHeader" Header="Homepage" Binding="{Binding Path=Homepage}" />
<dg:DataGridTemplateColumn MaxWidth="250" Header="Picture">
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Path=Picture}" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
<dg:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding Path=Picture, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemsSource>
<col:ArrayList>
<sys:String>Assets\Autumn Leaves.jpg</sys:String>
<sys:String>Assets\Butterfly.JPG</sys:String>
<sys:String>Assets\Green Sea Turtle.jpg</sys:String>
</col:ArrayList>
</ComboBox.ItemsSource>
</ComboBox>
</DataTemplate>
</dg:DataGridTemplateColumn.CellEditingTemplate>
</dg:DataGridTemplateColumn>
</dg:DataGrid.Columns>
</dg:DataGrid>
More Related Material:
Working with DataGridComboBoxColumn (Part1)
Working with DataGridComboBoxColumn (Part2)
Overview of the editing features in the DataGrid
Other Samples:
Custom sorting, column selection, single-click editing
DataGrid_V1_StockAndTemplateColumnsSample.zip
Comments
Anonymous
August 19, 2008
PingBack from http://hoursfunnywallpaper.cn/?p=2068Anonymous
August 20, 2008
Link Listing - August 19, 2008Anonymous
August 25, 2008
The comment has been removedAnonymous
August 26, 2008
DataGridComboBoxColumn is a column that deserves some special attention. What makes it a little uniqueAnonymous
August 26, 2008
Peter, I just wrote a post on it here: http://blogs.msdn.com/vinsibal/archive/2008/08/26/wpf-datagrid-working-with-datagridcomboboxcolumn.aspxAnonymous
September 16, 2008
There have been several questions on the WPF CodePlex discussion list relating to styling rows and columnsAnonymous
October 01, 2008
Introduction I’m going to talk a little on the editing features of the DataGrid. I will dive deep intoAnonymous
October 07, 2008
Having trouble seeing how I can go about adding a tooltip to the ElementStyle for a textblock in a text column: <dg:DataGridTextColumn Width="8.5*" Header="Key" DataFieldBinding="{Binding Key}"> <dg:DataGridTextColumn.ElementStyle> <Style TargetType="TextBlock"> <Setter Property="ToolTip" Value="{Binding Text, ElementName=TextBlock}" /> </Style> </dg:DataGridTextColumn.ElementStyle> </dg:DataGridTextColumn>Anonymous
October 07, 2008
hempels, If you want to bind to the Text property on the TextBlock itself you can do something like this, <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=Text}" />Anonymous
October 28, 2008
Hi I tried to select a template based on the data which is displayed by using a DataGridTemplateColumn with a CellTemplateSelector. But somehow the template is not selected correcly once the datagrid is scrolled. It seems that the template is only selected once and only for the data which initially fits into the grid. Any clues? ThanksAnonymous
October 30, 2008
Frank, There was a bug similar to this that was fixed in the v1 DataGrid. Try your code out with those bits and let me know if you are still having issues. More info on the v1 DataGrid here, http://blogs.msdn.com/vinsibal/archive/2008/10/22/wpf-datagrid-and-the-wpftoolkit-have-released.aspxAnonymous
November 03, 2008
If I set Width to 'Auto' or 'SizeToHeader', the columns are very narrow. If I use 'SizeToCells' the width seems to be correct. My expectation is that Auto would set column width to the max of the width necessary to display cells or width necessary to display title. Is this correct?Anonymous
November 04, 2008
David, Auto will set it to either SizeToHeader or SizeToCells, whichever is larger. Is that not what you are seeing?Anonymous
December 17, 2008
Hi Vinsibal, Do you have any link or tips regarding sorting DataGridTemplateColumns? I see that for template column, although CanUserSort is True, the column header based sorting wouldnt appear. What could we possibly do to achieve this? Thx a lot for all the WPF help as always...
- Vinit
Anonymous
December 18, 2008
Vinit, What about setting the SortMemberPath on the DataGridTemplateColumn?Anonymous
April 07, 2009
Since the release of the WPF DataGrid there have been several common patterns of questions that developersAnonymous
April 22, 2009
thanks a lot, i was looking for it from several days :)Anonymous
May 07, 2009
Hi all I am trying to make a selected data row editable and change all the cells to their respective edit templayed on a button click can anyone help with this. Thanks in advanceAnonymous
August 02, 2009
DataGrid column width remains large after decreasing font size even if the ColumnWidth is set to SizeToCell.Anonymous
August 31, 2009
Thanks a lot. I was looking for pasting an image into my DataGrid. Your tip about the "DataGridTemplateColumn" helped me. With the GenerateAutoColumns=True flag, the Toolkit automatically constructs TextColumns, so my image was shown as "System.Windows.Controls.Image" ... With best regards, RanuAnonymous
September 08, 2009
I'm wondering on how I can dynamically add Columns to the WPF DataGrid? I see you can add to the column collection however I cannot instantiate a new column. How would one go about doing this or do you have a post to refer me to?Anonymous
September 08, 2009
Oops I was instantiating the base DataGridColumn instead of DataGridTextColumn. Thanks anyways!Anonymous
October 20, 2009
Hello, there is a lot of good information here, but I was wondering if you could help me with something. I'm trying to change the Style of a DataGridRow based on the databound value of a DataGridTextColumn in the row. Is there a way to write a trigger for the whole row that is based on the value of the contents of one of the cells in the row? For instance, the first column in the row is 'Status' and when the databinding changes the value to 'ON' I want the whole row to light up green. Etc. Any help or points in the right direction would be greatly appreciated. Thanks. NeilAnonymous
October 20, 2009
Nevermind, figured it out, thanks!Anonymous
November 08, 2009
hi, your post is really helpful.Thanks I have a problem i want to show Row with CornerRadius so that i created one style as below.the border is ok but i cant see Values on the row even i bind the data.Please help me out and correct me. <Style x:Key="DataGridRowStyle" TargetType="{x:Type WpfToolkit:DataGridRow}"> <Setter Property="Header" Value="{Binding Id}"/> <Setter Property="SnapsToDevicePixels" Value="true"/> <Setter Property="OverridesDefaultStyle" Value="true"/> <Setter Property="HorizontalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="Foreground" Value="#000000"/> <Setter Property="Height" Value="25"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type WpfToolkit:DataGridRow}"> <Border Name="Border" Padding="2" SnapsToDevicePixels="true" Background="Red" CornerRadius="4" Margin="5,0,5,0"> <WpfToolkit:DataGridRowsPresenter Margin="2,0,2,0" DataContext="{Binding Path=TemplatedParent.View.Columns, RelativeSource={RelativeSource TemplatedParent}}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Background="Chocolate" /> </Border> <ControlTemplate.Triggers> <Trigger Property="ItemsControl.AlternationIndex" Value="1"> <Setter Property="Background" TargetName="Border" Value="#A5FFFFFF"></Setter> </Trigger> <Trigger Property="ItemsControl.AlternationIndex" Value="2"> <Setter Property="Background" TargetName="Border" Value="Red"></Setter> </Trigger> <Trigger Property="IsSelected" Value="true"> <Setter Property="Background" TargetName="Border" Value="#99B4C6"/> <Setter Property="Foreground" Value="#000000"/> </Trigger> <Trigger Property="IsMouseOver" Value="true"> <Setter Property="Background" TargetName="Border" Value="#c5d7e5"/> <Setter Property="Foreground" Value="#000000"/> </Trigger> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Foreground" Value="#000000"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> Thank you, SaruAnonymous
February 08, 2010
In your sample, how can I set foreground white in the HomePage column when I select the row? So, for example, if I use a TemplateColumn with a TextBox and not a DataGridTextColumn, how can I set a different foreground if the row is selected?Anonymous
February 09, 2010
Hi..I wanted 2 know how to reflect the changes in a particular datatemplate column when the rows in the datagrid are multiselected and a particular cell in that column has changed.The cells in the datatemplate column which have been multiselected have to be updated if any of the cells have been changed..Do u have any solution for this??Anonymous
March 15, 2010
Hi In your example regarding "Template Columns" you are using the property "FirstName" as the binding for a textbox in the template column's celleditingtemplate. In my app I have multiple template columns and I search for a way to get the property to which the textbox is bound (I have a handle to the template column) Can you post a sample of how to do this, as I using <celltemplate>.FindName gives me a compile error Regards Klaus