DataGridTemplateColumn for the WPF DataGrid

We’ve been looking at using the WPF DataGrid with its ItemsSource bound to data from a MySQL database (see the WPF index for a list of articles). One of the built-in DataGrid column types is the DataGridComboBoxColumn, which allows you to bind a list of items to a ComboBox that is displayed whenever the user wishes to edit the cell. When the cell is not being edited, the selected item from the ComboBox is displayed as an ordinary TextBlock.

This feature would be nice in a more general way. For example, a common field in a database is a date, so it would be nice if a DataGrid cell could display a date as text, but switch to a DatePicker control when the user wishes to edit the date. The DataGridTemplateColumn provides just this sort of feature.

Let’s look at how we could implement the date display/edit column. Going back to our comic book database, suppose we wished to store the date that we last read a particular issue. We insert a DateRead column in the MySQL database. This column has the MySQL data type of ‘date’. As before, we load the data from the ‘issues’ table into a WPF DataTable and use data binding to select which column from the DataTable is displayed in each column of the DataGrid. In the case of a template column, however, we need to specify the two types of cell (displaying and editing) separately. The XAML looks like this:

                <DataGridTemplateColumn Header="Date read">
                  <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                      <TextBlock Text="{Binding DateRead, Converter={StaticResource DateConverter}}" HorizontalAlignment="Center"/>
                    </DataTemplate>
                  </DataGridTemplateColumn.CellTemplate>
                  <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                      <DatePicker SelectedDate="{Binding DateRead}" />
                    </DataTemplate>
                  </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>

The CellTemplate part specifies what each cell displays when it’s not being edited. In this case we insert a TextBlock which is bound to the DateRead field. (We’ll look at the converter in a minute.) This means that the value of DateRead is displayed and since it’s a TextBlock (as opposed to a TextBox), there’s no way to edit it, which is fine.

The CellEditingTemplate shows what is displayed when the user double-clicks on the cell in order to edit its contents. In this case we display a DatePicker, which is a control with a small text area and a calendar icon which, when clicked, displays a calendar month. Clicking on the header of a DatePicker allows the user to choose another month or year, and a particular date is selected by clicking on the date’s day number. The selected date is then displayed in the text area. When the cell loses focus (when the user clicks somewhere else), the cell goes back to its TextBlock incarnation. The binding on the DatePicker ensures that the selected date is transmitted back to the DataTable.

That’s really all there is to using a template column. Remember that you can put any layout you like inside a DataTemplate, so if you wanted to get fancy you could even put in a Grid and then enclose multiple controls inside the Grid. Although the power is there, of course, putting in too much clutter makes for a bad user interface, so usually template columns use only one or two controls as we’ve done here with the DatePicker.

One final note concerning dates. Although we’re using a ‘date’ data type and not a ‘datetime’ type, for some reason, dates always seem to come attached to a time as well (usually 00:00:00), and unless we do something about it, the date and time get displayed in the TextBlock. This sort of thing is easy to fix using a converter for a data binding. We’ve seen data converters before, so this one doesn’t really introduce anything new. The idea is that we strip off the time portion of the date and return just the date portion. The C# code is:


  class DateConverter : IValueConverter
  {
    // Strip off the time part of the dateTime
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
      if (value != null && value.ToString().Length > 0)
      {
        string dateTime = value.ToString();
        int space = dateTime.IndexOf(" ");
        string date = dateTime.Substring(0, space);
        return date;
      }
      string empty = "";
      return empty;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }

We search for the blank that separates the date from the time and then use Substring() to get the date part of the string. The ConvertBack() bit should never be used.

Advertisements
Post a comment or leave a trackback: Trackback URL.

Comments

  • Dhaval Mehta  On February 8, 2013 at 6:28 PM

    Very informative. I am trying accomplish combobox in DataGridTemplateColumn. I am display the data on GUI. But after selection, On click of save, not able to read data via c# code. Please note that my code is generating GUI at runtime and read data from it at runtime. here is the method.

    private DataGridTemplateColumn AddDataGridComboBoxColumn(XmlNodeList _childnodeList, string header, DataTable table, List data = null)
    {
    var comboTemplate = new FrameworkElementFactory(typeof(ComboBox));
    List items = new List();
    for (int count = 0; count < _childnodeList.Count; count++)
    {
    items.Add(_childnodeList.Item(count).InnerText.ToString());
    }
    comboTemplate.SetValue(ComboBox.ItemsSourceProperty, items);
    comboTemplate.SetValue(ComboBox.SelectedItemProperty, items);
    DataGridTemplateColumn column = new DataGridTemplateColumn()
    {
    Header = header,
    CellTemplate = new DataTemplate() { VisualTree = comboTemplate },
    CellEditingTemplate = new DataTemplate() { VisualTree = comboTemplate }
    };

    DataColumn dtcolumn = new DataColumn();
    dtcolumn.Caption = header;
    dtcolumn.ColumnName = header;
    dtcolumn.DataType = typeof(ComboBox);
    table.Columns.Add(dtcolumn);
    //row[header] = items;
    return column;
    }

    Please let me know what i am missing here. My data row always comes with count 0 without any data.

    for (int j1 = 0; j1 < VisualTreeHelper.GetChildrenCount(item); j1++)
    {
    DependencyObject gridchild_child = VisualTreeHelper.GetChild(item, j1);
    DataGrid data = (DataGrid) ((TabItem)gridchild_child).Content;
    DataView table = (DataView)data.ItemsSource;

    Any will be appreciated…

  • Nani  On May 12, 2016 at 11:53 AM

    Hi ,
    Instead of TextBlock in the DataTemplate, i am using custom TextBlock control but i am unable assign text or do any work with this custom TextBlock control
    my control is “DataTextBlock”.
    [TemplatePart(Name = DataTextBlockName, Type = typeof(TextBlock))]
    public class DataTextBlock : Control
    {
    }

    Please Help in doing so.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: