Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / WPF

WPF DataGrid: Using DataTemplates for auto-generated columns

Rate me:
Please Sign up or sign in to vote.
4.78/5 (15 votes)
4 Nov 2009CPOL3 min read 110.5K   5.9K   39   2
An article on using templates with auto-generated columns in the WPF DataGrid.

Image 1

Introduction

The DataGrid is a control for displaying tabular data. Setting AutoGenerateColumns to true will automatically generate a column for each field. While this is quite useful, it does not give much control about the generated column. This article shows how you can use a DataTemplate for automatically generated columns with an attached property.

Implementation

The DataGrid provides an event called AutoGeneratingColumn. This event is fired for each individual column that is generated. You can adjust or cancel the generated column in the event handler. By setting DataGridAutoGeneratingColumnEventArgs.Column, you can provide your own column.

There is already a column class with support for templates, called DataGridTemplateColumn. This column has no binding support like DataGridBoundColumn. The data context for DataGridTemplateColumn is DataRowView. In order to reuse data templates for multiple fields, I created a custom class, CustomDataGridTemplateColumn, that extends DataGridTemplateColumn and adds binding support.

The AutoGeneratingColumn event handler is listed below. In this event handler, a new DataGridTemplateColumn column is created when the current column name is found in the AutoGenerateColumnCollection.

C#
public static void _grid_AutoGeneratingColumn(
    object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    DataGrid grid = sender as DataGrid;
    if (grid == null) { return; }

    AutoGenerateColumnCollection coll = GetColumns(grid);

    foreach (AutoGenerateColumn col in coll)
    {
        if (e.PropertyName == col.Column)
        {
            CustomDataGridTemplateColumn templateColumn = 
                    new CustomDataGridTemplateColumn();
            templateColumn.Header = e.Column.Header;
            if (col.CellTemplate != null)
            {
                templateColumn.CellTemplate = col.CellTemplate;
            }
            if (col.CellEditingTemplate != null)
            {
                templateColumn.CellEditingTemplate = col.CellEditingTemplate;
            }
            if (col.Binding != null)
            {
                templateColumn.Binding = col.Binding;
            }

            templateColumn.SortMemberPath = e.Column.SortMemberPath;
            e.Column = templateColumn;
            return;
        }
    }

    return;
}

The AutoGenerateColumnCollection originates from the attached property GenerateTemplateColumn.Columns. AutoGenerateColumnCollection is a class that extends ObservableCollection<AutoGenerateColumn> and is used to store the column names and templates.

The AutoGenerateColumn class is listed below:

C#
public class AutoGenerateColumn
{
    public string Column
    {
        get; set;
    }

    public DataTemplate CellTemplate
    {
        get; set;
    }

    public DataTemplate CellEditingTemplate
    {
        get; set;
    }

    public System.Windows.Data.BindingBase Binding
    {
        get; set;
    }
}

The AutoGeneratingColumn event handler is registered when the attached property changes. The PropertyChangedCallback for the attached property is shown below:

C#
private static void OnColumnsChanged(
    DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (e.NewValue != null)
    {
       AutoGenerateColumnCollection coll = 
                e.NewValue as AutoGenerateColumnCollection;

        DataGrid grid = coll.Owner as DataGrid;
        if (grid != null)
        {
            grid.AutoGeneratingColumn += _grid_AutoGeneratingColumn;
        }
    }

    if (e.OldValue != null)
    {
        AutoGenerateColumnCollection coll = 
                e.OldValue as AutoGenerateColumnCollection;

        DataGrid grid = coll.Owner as DataGrid;
        if (grid != null)
        {
            grid.AutoGeneratingColumn -= _grid_AutoGeneratingColumn;
        }
    }
}

Using the code

To use the code in your own project, you will need to include AutoGenerateColumn.cs and CustomDataGridTemplateColumn.cs.

You can specify the templates using the attached property GenerateTemplateColumn.Columns. The XAML below (from the sample application) shows how to specify templates for a column:.

XML
<toolkit:DataGrid x:Name="dataGrid1" AutoGenerateColumns="True">
    <local:GenerateTemplateColumn.Columns>
        <local:AutoGenerateColumn Column="ItemCreatedStamp"
            CellTemplate="{StaticResource TimeStampCellTemplate}"
            CellEditingTemplate="{StaticResource CellEditTemplate}"
            Binding="{Binding Path=ItemCreatedStamp}"/>
        <local:AutoGenerateColumn Column="ItemUpdatedStamp"
            CellTemplate="{StaticResource TimeStampCellTemplate}"        
            CellEditingTemplate="{StaticResource CellEditTemplate}"
            Binding="{Binding Path=ItemUpdatedStamp}"/>
        <local:AutoGenerateColumn Column="ItemLink"
            CellTemplate="{StaticResource HttpHyperCellTemplate}"
            CellEditingTemplate="{StaticResource CellEditTemplate}"
            Binding="{Binding Path=ItemLink}"/>
        <local:AutoGenerateColumn Column="ItemStatusId"
            CellTemplate="{StaticResource StatusCellTemplate}"
            CellEditingTemplate="{StaticResource CellEditTemplate}"
            Binding="{Binding Path=ItemStatusId}" />
    </local:GenerateTemplateColumn.Columns>
</toolkit:DataGrid>

A template can be as simple as:

XML
<DataTemplate x:Key="CellTemplate">
    <TextBlock Text="{Binding}" BorderThickness="0" Padding="0" />
</DataTemplate>

This way, you can easily customize the look of auto-generated columns.

Sample application

To build the demo project, you will need SQLite.NET and the WPF Toolkit.

The sample application includes a sample SQLite3 database (database.db) to demonstrate the use of templates. In the sample application, you can run any query and open different SQLite databases. By using templates, you can make the output more useful.

To query data from the main table:

SQL
SELECT * FROM Item

Running this query will show columns where templates are used. For example, a template is used for showing clickable links.

Sample application extra functionality

To make the sample application a bit more useful, code to allow the user to copy the data from the DataGrid to the clipboard is included. The code therefore was based on the code found in the links listed below:

TextBox context menu in DataGrid edit modus

While using the DataGrid, I noticed that the context menu of the TextBox was not working correctly in edit modus. When you right click on a TextBox in edit mode, the context menu is shown, but the items are disabled, which prevents you from using it. The reason for this is that when opening the context menu, the focus changes and the DataGridCell goes out of edit modus.

The solution is based on WPF DataGrid Sample: Locking input to the row being edited. In the CellEditEnding event, you cancel the event when a context menu is open. In order to detect whether a context menu is open, you can use the ContextMenuOpening and ContextMenuClosing events. The TextBox inside the DataGridCell uses TextEditorContextMenu.OnContextMenuOpening, which prevents you from receiving the events. In order to catch the event, you can use a custom ContextMenu like shown below:

XML
<ContextMenu x:Key="textBoxMenu">
    <MenuItem Command="ApplicationCommands.Cut">
        <MenuItem.Icon>
            <Image Source="Resources\cut.png"/>
        </MenuItem.Icon>
    </MenuItem>
    <MenuItem Command="ApplicationCommands.Copy">
        <MenuItem.Icon>
            <Image Source="Resources\copy.png"/>
        </MenuItem.Icon>
    </MenuItem>
    <MenuItem Command="ApplicationCommands.Paste">
        <MenuItem.Icon>
            <Image Source="Resources\paste.png"/>
        </MenuItem.Icon>
    </MenuItem>
</ContextMenu>

You can set this context menu in various ways, when using auto-generated columns directly on the TextBox in the template, or using a style. See _grid_AutoGeneratingColumn in the sample application for non-templated auto generated columns.

Points of interest

While using templates for auto-generated columns is useful, the templates are applied based on the column name. When querying different data with the same column names, this can lead to unintended results.

History

  • 4-11-2009: Initial article upload.

License

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


Written By
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionTrying it on VS2015 Pin
ErrrCmon!25-Apr-18 23:31
ErrrCmon!25-Apr-18 23:31 
GeneralTurning it more useful Pin
paulosmasher6-Nov-09 2:57
paulosmasher6-Nov-09 2:57 
I really liked this article, I'm doing something with the same purpose with the WPF DataGrid.
I made a few changes in your code, I'm using the PropertyType instead of the PropertyName.

In the method _grid_AutoGeneratingColumn I compare the PropertyType with the AutoGenerateCollumn collection, and If it matches I choose the right template and bind the content to the e.PropertyName.
Now you do not need to worry with the column name and stuff.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.