Partager via


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:

Dissecting the Visual Layout

Working with DataGridComboBoxColumn (Part1)

Working with DataGridComboBoxColumn (Part2)

Overview of the editing features in the DataGrid

Other Samples:

ScrollViewer with ToolTip

Custom sorting, column selection, single-click editing

Tri-state sorting

 

DataGrid_V1_StockAndTemplateColumnsSample.zip

Comments

  • Anonymous
    August 19, 2008
    PingBack from http://hoursfunnywallpaper.cn/?p=2068

  • Anonymous
    August 20, 2008
    Link Listing - August 19, 2008

  • Anonymous
    August 25, 2008
    The comment has been removed

  • Anonymous
    August 26, 2008
    DataGridComboBoxColumn is a column that deserves some special attention. What makes it a little unique

  • Anonymous
    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.aspx

  • Anonymous
    September 16, 2008
    There have been several questions on the WPF CodePlex discussion list relating to styling rows and columns

  • Anonymous
    October 01, 2008
    Introduction I’m going to talk a little on the editing features of the DataGrid. I will dive deep into

  • Anonymous
    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? Thanks

  • Anonymous
    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.aspx

  • Anonymous
    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 developers

  • Anonymous
    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 advance

  • Anonymous
    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, Ranu

  • Anonymous
    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. Neil

  • Anonymous
    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, Saru

  • Anonymous
    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