Getting Started with WPF DataGrid: Columns, Binding, Selection, and Editing

Published:

After working with the ListView control, the next useful control to look at is DataGrid. Whether in ASP.NET web development or WinForms desktop applications, DataGrid-style controls are used very often. Their main purpose is straightforward: display data in rows and columns, while still allowing flexible control over how each field is shown and edited.

This example focuses on the WPF DataGrid and several common tasks: defining columns, binding database data, using a ComboBox column, controlling selection behavior, and saving edited data.

DataGrid column types

By default, once the ItemsSource property is assigned, a WPF DataGrid can automatically generate columns according to the bound data. WPF provides several built-in column types for different kinds of content.

<table> <thead> <tr> <th>Column type</th> <th>Displayed data</th> <th>Data type</th> </tr> </thead> <tbody> <tr> <td>DataGridHyperlinkColumn</td> <td>URI data displayed as a hyperlink.</td> <td>URI</td> </tr> <tr> <td>DataGridComboBoxColumn</td> <td>Enum data, or other values that should be selected from a drop-down list.</td> <td>Enum, String</td> </tr> <tr> <td>DataGridTextColumn</td> <td>Text content.</td> <td>String</td> </tr> <tr> <td>DataGridCheckBoxColumn</td> <td>Boolean values.</td> <td>Bool</td> </tr> </tbody> </table>

When creating a DataGrid, the AutoGenerateColumns property determines whether columns should be generated automatically. If a DataGrid contains both auto-generated columns and manually defined columns, the custom columns are created first.

DataGrid columns example

Building the DataGrid interface

Create a WPF window named WindowGrid in Visual Studio 2013.

Create WindowGrid

Next, locate the DataGrid control in the Visual Studio toolbox and double-click it to add it to the window. At first, the DataGrid appears as a small box on the form. Compared with the familiar WinForms DataGrid, the WPF version may look quite different before columns and data are configured.

Add DataGrid from toolbox

DataGrid placed on the window

To add columns, select the small DataGrid box on the window, then open the Properties panel on the right side of Visual Studio 2013.

Open DataGrid properties

In the Properties window, click the button on the Columns row. A column editor dialog will appear.

DataGrid column editor

Choose the required column type and click Add.

Add DataGrid columns

In this example, five DataGridTextColumn columns and one DataGridComboBoxColumn column are added. After these columns are created, the control starts to look much closer to a traditional DataGrid in WinForms.

Configured DataGrid columns

The completed XAML for the window is as follows:

<Window x:Class="WpfApp1.WindowGrid"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Grid示例" Height="400" Width="600">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="22"/>
        </Grid.RowDefinitions>
        <DataGrid x:Name="gridCitys"  Grid.Row="0" HorizontalAlignment="Left" VerticalAlignment="Top" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding CityID}" ClipboardContentBinding="{x:Null}" Header="CityID"/>
                <DataGridTextColumn Binding="{Binding CityName}" ClipboardContentBinding="{x:Null}" Header="CityName"/>
                <DataGridTextColumn Binding="{Binding ZipCode}" ClipboardContentBinding="{x:Null}" Header="ZipCode"/>
                <DataGridComboBoxColumn x:Name="cboProvince" ClipboardContentBinding="{x:Null}" Header="ProvinceID" SelectedValuePath="ProvinceID" SelectedValueBinding="{Binding Path=ProvinceID,UpdateSourceTrigger=PropertyChanged}"  DisplayMemberPath="ProvinceName" SelectedItemBinding="{x:Null}" >
                </DataGridComboBoxColumn>
                <DataGridTextColumn Binding="{Binding DateCreated}" ClipboardContentBinding="{x:Null}" Header="DateCreated"/>
                <DataGridTextColumn Binding="{Binding DateUpdated}" ClipboardContentBinding="{x:Null}" Header="DateUpdated"/>
            </DataGrid.Columns>
        </DataGrid>
        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" >
            <TextBlock Text="显示信息" TextAlignment="Center"  />
            <TextBox Name="txtMsg" IsReadOnly="True" Text="" Width="320" TextAlignment="Center" />
        </StackPanel>
        <WrapPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button  HorizontalAlignment="Right"  Name="btnRefresh" Height="22" VerticalAlignment="Top" Width="65" Click="btnRefresh_Click">刷新</Button>
            <Button  HorizontalAlignment="Right"  Name="btnUpdate" Height="22" VerticalAlignment="Top" Width="65" Click="btnUpdate_Click"  >更新</Button>
        </WrapPanel>
    </Grid>
</Window>

Code-behind for the DataGrid example

The example uses Entity Framework 6.1 to read city data from the S_City table in a local database named Test, and province data from the S_Province table. The result is then displayed in the DataGrid through data binding.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using WpfApp1.Models;
namespace WpfApp1
{
    /// <summary>
    /// WindowGrid.xaml 的交互逻辑
    /// </summary>
    public partial class WindowGrid : Window
    {
        public WindowGrid()
        {
            InitializeComponent();
            Database.SetInitializer<TestDBContext>(null);
        }
        private void btnRefresh_Click(object sender, RoutedEventArgs e)
        {
            BindDrp();
            GetData();
        }
        TestDBContext db = new TestDBContext();
        protected void GetData()
        {
            List<S_City> list = db.S_City.ToList<S_City>();
            gridCitys.ItemsSource = list;
        }
        protected void BindDrp()
        {
            List<S_Province> list = db.S_Province.ToList<S_Province>();
            cboProvince.ItemsSource = list;
            ProvinceList = list;
        }
        public List<S_Province> ProvinceList
        { get; set; }
        private void btnUpdate_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                S_City city = (S_City)gridCitys.SelectedItem;
                city.DateUpdated = DateTime.Now;
                txtMsg.Text = city.ProvinceID + "//" + city.CityName;
                S_City modifyCity = db.S_City.Find(city.CityID);
                modifyCity = city;
                db.SaveChanges();
                txtMsg.Text += "保存成功!";
            }
            catch (Exception ex)
            {
                txtMsg.Text += ex.Message;
            }
        }
    }
}

After writing the code, press F5 to run the program and click the Refresh button. The first result may show extra columns generated automatically by the DataGrid.

DataGrid with auto-generated columns

Those automatically generated columns are not needed here, so AutoGenerateColumns should be set to False. After running the program again, the grid displays only the manually defined columns.

DataGrid with manual columns only

At this point, the drop-down list can be displayed, but another problem appears: the column still does not show the expected value by default. The issue is related to how DataGridComboBoxColumn is bound.

Binding a ComboBox column in DataGrid

DataGridComboBoxColumn has specific requirements for its data source. To fill the drop-down list, the ComboBox ItemsSource should be set using one of these approaches:

  • A static resource, using the StaticResource markup extension.
  • A static code entity, using the x:Static markup extension.
  • An inline collection of ComboBoxItem objects.

In real projects, it is common to need one or more ComboBox columns inside a DataGrid. The obvious choice is DataGridComboBoxColumn, but if the column uses an ItemsSource bound to objects from code-behind, it may not work as expected without the correct binding settings.

For example, after clicking Refresh, the drop-down list initially contains no data. Looking at the XAML, there is no useful binding defined for the foreground column, and the code-behind does not yet bind the column correctly either.

<DataGridComboBoxColumn ClipboardContentBinding="{x:Null}" Header="ProvinceID" SelectedValueBinding="{x:Null}" SelectedItemBinding="{x:Null}" TextBinding="{x:Null}"/>

ComboBox column without data

Keeping the foreground XAML unchanged:

<DataGridComboBoxColumn x:Name="cboProvince" ClipboardContentBinding="{x:Null}" Header="ProvinceID" SelectedValueBinding="{x:Null}" SelectedItemBinding="{x:Null}" TextBinding="{x:Null}"/>

The code-behind can bind the province list to the ComboBox column like this:

        protected void BindDrp()
        {
            List<S_Province> list = db.S_Province.ToList<S_Province>();
            cboProvince.ItemsSource = list;
        }

With this change, the binding succeeds, but the displayed value is still not the one we want.

ComboBox list bound but display value incorrect

The next attempt is to bind the DataGridComboBoxColumn in XAML like this:

<DataGridComboBoxColumn x:Name="cboProvince" ClipboardContentBinding="{x:Null}" Header="ProvinceID" SelectedValuePath="ProvinceID" DisplayMemberPath="ProvinceName" SelectedItemBinding="{x:Null}" TextBinding="{Binding ProvinceName}"/>

After running the program, the drop-down list itself displays correctly. However, no matter how the value is changed, the default content shown in the DataGrid under ProvinceID remains blank.

ProvinceID column remains blank

The final working version uses SelectedValuePath, SelectedValueBinding, and DisplayMemberPath together. After this change, once the DataGrid loads the data, the ProvinceID column displays the intended province name correctly.

<DataGridComboBoxColumn x:Name="cboProvince" ClipboardContentBinding="{x:Null}" Header="ProvinceID" SelectedValuePath="ProvinceID" SelectedValueBinding="{Binding Path=ProvinceID,UpdateSourceTrigger=PropertyChanged}"  DisplayMemberPath="ProvinceName" SelectedItemBinding="{x:Null}" >
</DataGridComboBoxColumn>

ComboBox column displaying province names correctly

DataGrid selection modes

By default, DataGrid uses full-row selection and allows multiple rows to be selected at the same time.

Default full-row selection

This behavior can be changed through the SelectionMode and SelectionUnit properties.

SelectionUnit controls what kind of unit can be selected:

<table> <thead> <tr> <th>Member name</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>Cell</td> <td>Selects a cell. Clicking a cell selects that cell. Clicking row or column headers does nothing.</td> </tr> <tr> <td>FullRow</td> <td>Selects a full row. Clicking a cell or row header selects the entire row.</td> </tr> <tr> <td>CellOrRowHeader</td> <td>Allows selecting cells, and also selecting a full row by clicking the row header.</td> </tr> </tbody> </table>

SelectionMode controls whether one or multiple units can be selected:

<table> <thead> <tr> <th>Member name</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>Extended</td> <td>Allows selecting multiple units, either cells or full rows depending on SelectionUnit.</td> </tr> <tr> <td>Single</td> <td>Allows selecting only one unit, either a cell or a full row depending on SelectionUnit.</td> </tr> </tbody> </table>

For example:

<DataGrid x:Name="gridCitys"  Grid.Row="0" HorizontalAlignment="Left" VerticalAlignment="Top" AutoGenerateColumns="False" SelectionUnit="Cell" SelectionMode="Extended">

The effect is shown below.

Cell selection with extended mode

Editing data in DataGrid

By default, the WPF DataGrid allows data to be edited directly in the grid. If editing should be disabled, the IsReadOnly property can be used to make the DataGrid read-only.

In this example, the first row is selected in the Grid示例 window.

Select the first row

Then the ProvinceID value of the first row is modified by choosing 内蒙古自治区 from the drop-down list.

Modify ProvinceID with ComboBox

Click the Update button to save the change.

Click Update to save

Before saving, the data appears as follows:

Result before saving

After saving, the edited value is written back through the update logic in the code-behind.