Displaying Data in Tables with WPF’s DataGrid Control

wpf datagridIn Windows Presentation Foundation (WPF), the datagrid is a highly-adaptable control for displaying information in tables in your Windows desktop applications. While naturally suited for tabular data, it is flexible enough to present many different kinds of dynamic content. Its look and feel is customizable, it offers high performance even with large data sets, and many user interface interactions (such as re-ordering the information, and multiple selection modes) are already supported by the control. Using it from desktop applications is very similar to how it can be used on the web with Silverlight, or in Windows Phone applications.

In this short guide, you will see how to add a datagrid to a WPF project using extensible application markup language (XAML) and C#. It is not a full tutorial of C# – you can find in-depth instruction on C# and working with Visual Studio at Udemy.com – and nor does it cover the entire range of options and techniques available to programmers using WPF. Learn C# Part III covers WPF controls and key concepts such a data binding.

Declaring a DataGrid in XAML

You can insert datagrids into XAML using the appropriately-named element DataGrid. The example declaration below also sets a few of the common properties:

<DataGrid Name="dgTest" CanUserAddRows="False" CanUserResizeColumns="True" CanUserSortColumns="True" IsReadOnly="True" />

The five properties set here are:

  1. Name – gives the datagrid a name so that it can be accessed from C# in the code-behind script.
  2. CanUserAddRows – when false, the blank row used for adding data is removed.
  3. CanUserResizeColumns – allows the user to change the width of columns in the table.
  4. CanUserSortColumns – allows the user to sort the data in the table by clicking on column names.
  5. IsReadOnly – when true, prevents the user from double-clicking a cell to edit its contents.

At this stage, the grid displays no content. To show information, you must bind the datagrid to a suitable data source.

Data binding is a fairly extensive topic, and cannot be covered in depth here. In the examples that follow, a data source is created using an ObservableCollection of a custom class in the window’s code-behind script. This is not always the best approach to take – you will usually be using DataAdapters and DataTables to retrieve information from a database, or external files, and bind it to a datagrid.

Create the binding by setting the grid’s ItemSource property to inherit from the data context. In the code-behind script, after the data collection is populated, the collection is set as the DataContext property of the grid, and this causes the user interface to update and display the information.

XAML:

<DataGrid Name="dgTest" CanUserResizeColumns="True" CanUserAddRows="False" CanUserSortColumns="True" IsReadOnly="True" ItemsSource="{Binding}" />

Code-behind (C#):

/* 
 * Udemy.com
 * Displaying Data in Tables with WPF's DataGrid Control
 */

using System;
using System.Collections.ObjectModel;
using System.Windows;

namespace WpfApplication1
{
    public class Track
    {
        private String _t;
        private String _a;
        private int _n;

        public String title {
            get { return _t; } 
            set { _t = value; } 
        }

        public String artist {
            get { return _a; }
            set { _a = value; }
        }

        public int number {
            get { return _n; }
            set { _n = value; }
        }
    }

    public partial class MainWindow : Window
    {
        public ObservableCollection data = new ObservableCollection();

        public MainWindow()
        {
          InitializeComponent();

          data.Add(new Track(){title="Think", artist="Aretha Franklin", number=7});
          data.Add(new Track(){title="Minnie The Moocher", artist="Cab Calloway", number=9});
          data.Add(new Track(){title="Shake A Tail Feather", artist="Ray Charles", number=4});
          dgTest.DataContext = data;
        }
    }
}

Manually Defining Columns

In the preceding example, the columns and their headings were automatically read from the public properties of the Track class. You can manually define the columns of a table by adding a suitable collection to the datagrid’s columns property. This can be done in XAML as shown below:

<DataGrid Name="dgTest" AutoGenerateColumns="False" IsReadOnly="True" CanUserResizeColumns="True" CanUserAddRows="False" CanUserSortColumns="True" ItemsSource="{Binding}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Track Title" Binding="{Binding title}" />
        <DataGridTextColumn Header="Artist" Binding="{Binding artist}" />
        <DataGridTextColumn Header="Track Number" Binding="{Binding number}" />
    </DataGrid.Columns>
</DataGrid>

First, the AutoGenerateColumns property is changed to false to instruct .NET not to use the properties from the data as columns; then the desired columns are specified by adding DataGridTextColumns to the markup. The property Header sets the text that is displayed at the top of the column.

There are five different types of column element that can be used to display information in a data grid:

ToDo This
Display numbers and text stringsDefine a column using DataGridTextColumn.
Display hyperlinksDefine a column using DataGridHyperlinkColumn.
Display checkboxesDefine a column using DataGridCheckBoxColumn and bind to a boolean or numeric value (non-zero values will be checked).
Display combo boxesDefine a column using DataGridComboBoxColumn, bind to a value, and set the column’s own ItemSource property to an enumerated list of possible values.
Create customized and highly-styled columnsUse a DataGridTemplateColumn.

Setting the Selection Mode

The default settings of a datagrid allows the user to select entire rows (and multiple rows) by clicking the left mouse button. With this, the SelectionMode property is Extended, and the SelectionUnit is FullRow.

These two properties can be changed:

  • Changing the SelectionMode to Single only allows the user to select a single item.
  • Changing the SelectionUnit to Cell only allows for table cells to be selected, not whole rows.
  • Changing the SelectionUnit to CellOrRowHeader allows individual cells to be selected by clicking them, and entire rows to be selected by clicking the row headers.

You can use the SelectionChanged event to respond to the user’s clicks on the table. This is done by writing an event handler into the code-behind script:

private void dgTest_SelectionChanged(object sndr, System.Windows.Controls.SelectionChangedEventArgs e) {
    // do something
}

And then specifying the name of the handler in the datagrid’s XAML declaration:

<DataGrid Name="dgTest" IsReadOnly="True" CanUserResizeColumns="True" CanUserAddRows="False" CanUserSortColumns="True" ItemsSource="{Binding}" SelectionMode="Single" SelectionUnit="FullRow" SelectionChanged="dgTest_SelectionChanged" />

Grouping Rows

The WPF datagrid control supports grouping rows by their column values – making complicated tables easier to read – and this can be extended with expand and collapse functionality.

To use column-based grouping, you must bind the data grid to a CollectionView, and this is then bound to the data itself. You can define a CollectionViewSource in the window’s resources and choose a column (defined with a PropertyGroupDescription element) to group the rows.

<Window.Resources>
    <CollectionViewSource x:Key="cvs">
        <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription PropertyName="artist" />
        </CollectionViewSource.GroupDescriptions>
    </CollectionViewSource>
</Window.Resources>

Next, the data grid is created with its ItemSource property statically bound to this CollectionViewSource:

<DataGrid Name="dgTest" IsReadOnly="True" CanUserResizeColumns="True" CanUserAddRows="False" CanUserSortColumns="True" ItemsSource="{Binding Source={StaticResource cvs}}" />

After the ObservableCollection has been populated with data in the window’s constructor in C#, the CollectionViewSource’s Source property can then be set; for brevity only the constructor is shown here:

public MainWindow()
{
  InitializeComponent();

  data.Add(new Track(){title="Think", artist="Aretha Franklin", number=7});
  data.Add(new Track(){title="Minnie The Moocher", artist="Cab Calloway", number=9});
  data.Add(new Track(){title="Shake A Tail Feather", artist="Ray Charles", number=4});

  CollectionViewSource cvs = (CollectionViewSource)FindResource("cvs");
  cvs.Source = data;
}

If you would prefer to do more of the work from the code-behind script, you can create the CollectionViewSource in C# code, and only declare the datagrid itself in XAML. To create the necessary CollectionViewSource and PropertyGroupDescription objects, using directives must be added for System.ComponentModel and System.Windows.Data.

public MainWindow()
{
  InitializeComponent();

  data.Add(new Track(){title="Think", artist="Aretha Franklin", number=7});
  data.Add(new Track(){title="Minnie The Moocher", artist="Cab Calloway", number=9});
  data.Add(new Track(){title="Shake A Tail Feather", artist="Ray Charles", number=4});
    
  CollectionViewSource cvs = new CollectionViewSource();
  cvs.Source = data;
  cvs.GroupDescriptions.Add(new PropertyGroupDescription("artist"));
  dgTest.DataContext = cvs;
}

Finally, for the group headers to actually appear when the program is run, you must define their visual appearance in the datagrid’s GroupStyle property.

<DataGrid Name="dgTest" IsReadOnly="True" CanUserResizeColumns="True" CanUserAddRows="False" CanUserSortColumns="True" ItemsSource="{Binding}">
    <DataGrid.GroupStyle>
        <GroupStyle>
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <TextBlock FontWeight="Bold" FontSize="14" Text="{Binding Name}"/>
                </DataTemplate>
            </GroupStyle.HeaderTemplate>
        </GroupStyle>
    </DataGrid.GroupStyle>
</DataGrid>

Styling the DataGrid

One of the most common visual techniques when using tables (both in WPF and on the web) is to create rows with alternating background colors. The datagrid includes a specific property for doing this, AlternatingRowBackground, and it can be set from XAML:

<DataGrid Name="dgTest" IsReadOnly="True" CanUserResizeColumns="True" CanUserAddRows="False" CanUserSortColumns="True" ItemsSource="{Binding}" AlternatingRowBackground="Azure" />

When used in combination with AlternationCount, you can change the background of every nth row – where n is the value of AlternationCount.

In general, the visual appearance of a datagrid should be controlled by application-wide themes, styles, and templates, rather than setting the properties for a single grid. For more information about styling in WPF, you can refer to Learn C# 2010 Part III, which is part of a very thorough course explaining the essentials of C# and WPF.