Using the WPF Toolkit DataGrid

Skill

Using the WPF Toolkit DataGrid

Posted in:

WPF comes with a large number of built in controls, but from the beginning it has lacked something that many application developers find extremely important - a DataGrid. You can use the ListView to create something approximating a DataGrid (I've talked about it in a couple different tutorials), but it is a lot of work and not particularly straightforward. Thankfully, Microsoft realizes how important a full-featured DataGrid is - and how you probably don't want to wait for the next version of WPF to be able to use one. This is where the WPF Toolkit comes in. The WPF Toolkit is "a collection of WPF features and components that are being made available outside of the normal .NET Framework ship cycle" which to me translates as "handy new controls I don't have to wait for".

The WPF Toolkit has a couple different controls, but the big one is the DataGrid - and that is what we will be exploring today. You can grab the toolkit from here. It is an MSI install package that will drop a couple DLLs off in a "WPF Toolkit" directory in your program files directory. Once you have that installed, the first thing you need to do to be able to use the toolkit is add it as a reference to your project:

Add Reference Dialog

Ok, now that we have the dll added as a reference, it is time to start actually using the DataGrid. The basics of using the DataGrid are incredibly easy - for example, take a look at the following XAML:

<Window x:Class="SOTC_DataGridExample.Window1" Name="This"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
   Title="DataGrid Example" Height="300" Width="300">
  <dg:DataGrid ItemsSource="{Binding ElementName=This, Path=GameData}" Margin="5" />
</Window>

First off, to use the toolkit, you have to add a new namespace line in XAML for the toolkit. In this case, I called the namespace dg. Once you have that, you can drop down a XAML tag for the DataGrid. For this example, the DataGrid is bound to an ItemSource called GameData - let's take a look at the backing C# code to find out what that is:

public partial class Window1 : Window
{
  private DataTable _GameData;

  public Window1()
  {
    _GameData = new DataTable();
    _GameData.Columns.Add(new DataColumn("Game Name", typeof(string)));
    _GameData.Columns.Add(new DataColumn("Creator", typeof(string)));
    _GameData.Columns.Add(new DataColumn("Publisher", typeof(string)));

    var row = _GameData.NewRow();
    _GameData.Rows.Add(row);
    row["Game Name"] = "World Of Warcraft";
    row["Creator"] = "Blizzard";
    row["Publisher"] = "Blizzard";

    row = _GameData.NewRow();
    _GameData.Rows.Add(row);
    row["Game Name"] = "Halo";
    row["Creator"] = "Bungie";
    row["Publisher"] = "Microsoft";

    row = _GameData.NewRow();
    _GameData.Rows.Add(row);
    row["Game Name"] = "Gears Of War";
    row["Creator"] = "Epic";
    row["Publisher"] = "Microsoft";

    InitializeComponent();
  }

  public DataTable GameData
  { get { return _GameData; } }
}

GameData is a DataTable. If you have played around with the DataGridView in WinForms, you probably recognize the name - it was the easy way to hook up a DataGridView to a database. In this case, I didn't bring a whole database into this sample app - I just populated the DataTable by hand with a little bit of code.

One of the handy things with the WPF DataGrid that with something like a DataTable, the DataGrid can do pretty much everything that we need automatically. With only the code above, we get an application that looks like this:

Screenshot of DataGrid

The DataGrid will, if it can, automatically generate the columns for your data (in this case, it created the three columns "Game Name", "Creator" and "Publisher"). In addition to that, though, you automatically get edit, add, and remove capabilities. If we attach to the RowChanged and RowDeleted events on the DataTable, we can see that changes to the DataGrid are automatically getting pushed back into the DataTable:

private void GameDataRowChanged(object sender, DataRowChangeEventArgs e)
{
  Console.WriteLine("----Row Changed----");
  Console.WriteLine("Action: " + e.Action);
  if (e.Action != DataRowAction.Delete)
  {
    Console.Write("Values: ");
    foreach (var val in e.Row.ItemArray)
    { Console.Write(val + ", "); }
    Console.WriteLine();
  }      
  Console.WriteLine("-------------------");
}

After adding a new row to the app, we get this output:

----Row Changed----
Action: Add
Values: My Game, A Creator, A Publisher,
-------------------

If the DataTable was connected up to a database, these changes would get automatically pushed, with little to no work on our part!

Ok, time to get a bit more advanced. Let's say we wanted the Publisher column to be a ComboBox, and we wanted to add a CheckBox column for if the game is available on the Xbox:

Second App Example

Because we have custom column types in there, we can't use automatic column generation anymore. This makes the XAML much more interesting, so let's take a look at it:

<Window x:Class="SOTC_DataGridExample.Window1" Name="This"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
   Title="DataGrid Example" Height="300" Width="300">
  <dg:DataGrid ItemsSource="{Binding ElementName=This, Path=GameData}"
              Margin="5" AutoGenerateColumns="False">
    <dg:DataGrid.Columns>
      <dg:DataGridTextColumn Binding="{Binding Game Name}" Header="Game Name" />
      <dg:DataGridTextColumn Binding="{Binding Creator}" Header="Creator" />
      <dg:DataGridComboBoxColumn Header="Publisher" x:Name="_PublisherCombo"
                                SelectedItemBinding="{Binding Publisher}" />
      <dg:DataGridCheckBoxColumn Binding="{Binding On Xbox}"
                                Header="On Xbox 360" />
    </dg:DataGrid.Columns>
  </dg:DataGrid>
</Window>

Looks kind of similar to how you would use the GridView, doesn't it? However, unlike there, here we have a couple built in column types that we can use - in this case, DataGridTextColumn for editable text, DataGridComboBoxColumn for when we want a combo box, and DataGridCheckBoxColumn for when we want a checkbox. The basics are the same for all three of them - the Header property sets the name of the column, and the Binding property (or SelectedItemBinding in the case of the DataGridComboBoxColumn) binds the value of that column to a column in the backing data store (whether that is a DataTable, some XML, or some other type of data).

The dg:DataGridCheckBoxColumn pretty much works without any extra work, but the DataGridComboBoxColumn takes a little more care. We have to provide it with a collection of entries for the combo box - in this case a collection of publisher names. You can do another binding (although beware, the DataContext will be the particular row in your data source, and so the binding may be difficult), but in this case I just set it to a collection of strings in code:

using System;
using System.Collections.Generic;
using System.Data;
using System.Windows;

namespace SOTC_DataGridExample
{
  public partial class Window1 : Window
  {
    private DataTable _GameData;

    public Window1()
    {
      _GameData = new DataTable();
      _GameData.Columns.Add(new DataColumn("Game Name", typeof(string)));
      _GameData.Columns.Add(new DataColumn("Creator", typeof(string)));
      _GameData.Columns.Add(new DataColumn("Publisher", typeof(string)));
      _GameData.Columns.Add(new DataColumn("On Xbox", typeof(bool)));

      var row = _GameData.NewRow();
      _GameData.Rows.Add(row);
      row["Game Name"] = "World Of Warcraft";
      row["Creator"] = "Blizzard";
      row["Publisher"] = "Blizzard";
      row["On Xbox"] = false;

      row = _GameData.NewRow();
      _GameData.Rows.Add(row);
      row["Game Name"] = "Halo 3";
      row["Creator"] = "Bungie";
      row["Publisher"] = "Microsoft";
      row["On Xbox"] = true;

      row = _GameData.NewRow();
      _GameData.Rows.Add(row);
      row["Game Name"] = "Gears Of War";
      row["Creator"] = "Epic";
      row["Publisher"] = "Microsoft";
      row["On Xbox"] = true;

      _GameData.RowChanged += GameDataRowChanged;
      _GameData.RowDeleted += GameDataRowChanged;

      InitializeComponent();

      _PublisherCombo.ItemsSource = new List<string>() { "Activision", "Ubisoft",
        "Microsoft", "Blizzard", "Nintendo", "Electronic Arts",
        "Take-Two Interactive" };
    }

    private void GameDataRowChanged(object sender, DataRowChangeEventArgs e)
    {
      Console.WriteLine("----Row Changed----");
      Console.WriteLine("Action: " + e.Action);
      if (e.Action != DataRowAction.Delete)
      {
        Console.Write("Values: ");
        foreach (var val in e.Row.ItemArray)
        { Console.Write(val + ", "); }
        Console.WriteLine();
      }      
      Console.WriteLine("-------------------");
    }

    public DataTable GameData
    { get { return _GameData; } }
  }
}

Well, that is it for this introduction to the WPF Toolkit DataGrid. This really only scratches the surface of what you can do with the DataGrid - it is quite the complex control. I will leave you with one tidbit, though - as much as possible the DataGrid follows standard WPF rules - so all the rules for styling, control templates, and data templates still apply. You can look forward to details on that type of customization in a future tutorial - for now, you will have to be satisfied with experimenting on your own. If you would like somewhere to start, you can grab the Visual Studio solution for this example project below - but remember, for it to run, you will need to install the toolkit.

Anonymous
06/21/2009 - 15:12

I'm starting off with WPF and I'm amazed with all the stuff that can be done with little work.
Great article Tallest, very useful for me! :-)
Thanks!!
Cau

reply

webgun60
06/30/2009 - 09:30

I get an error when trying to set the DataGrid ItemsSource in the xaml.

"Property 'ItemsSource' of type 'IEnumerable' cannot be specified as a string".

It appears to work in the authors example. Any ideas ?

reply

The Tallest
06/30/2009 - 12:37

That sounds like your binding syntax is not quite right. Do you have curly braces around the binding? Post that snippet of XAML here and I'll take a look at it.

reply

Anonymous
07/05/2009 - 03:01

I am also a WPF fan, You can directly contact with me by MSN: zhoujiguo1985@live.cn. so we can instantly
communicate with each other. thanks.

reply

Bill J
12/13/2009 - 16:24

Thanks for the tutorial. I enjoyed playing with this project very much and learn a couple of things. I still have two questions:

1) Can you provide a little more info on the purpose of the multi-cast stuff - i. e.:
_GameData.RowChanged += GameDataRowChanged;
_GameData.RowDeleted += GameDataRowChanged;

2) For some reason I get a 5th blank column in my project. Not quit sure why. Is their a property I can set to turn this off?

reply

Anonymous
05/08/2010 - 05:14

why does not the codeplex does not tell use datatable at the first place for data binding. ie system.datatable.

You should know this point, when give software to developer to develop , you need to provide some kind information about the tools and features and code sample to work. Other wise developers will be looking a needle in the haystack.

It really sucks

reply

mbadawi23
06/09/2010 - 16:24

How can I have more than one DataGridComboBoxColumn but tie them to one list?

reply

RandallFlagg
07/09/2010 - 06:55

As always this is a great tutorial.

I just wanted to point(and please tell me if I am wrong) that if you will put this line
_GameData.Rows.Add(row);
at the end of the row creation it will be better because in your way the event is being fired for every change where as if you put it at the end it will be fired only once

DataRow row = _GameData.NewRow();
row["Game Name"] = "World Of Warcraft";
row["Creator"] = "Blizzard";
row["Publisher"] = "Blizzard";
row["On Xbox"] = false;
_GameData.Rows.Add(row);

reply

The Reddest
07/09/2010 - 07:33

Yes and no. If the collection was bound to a UI control prior to it being populated, then your approach would save a lot of processing. The DataTable in this example is being created before the InitializeComponent call, so the DataGrid has not yet been bound to the table. Either way, though, I'd recommend your suggestion.

reply

RandallFlagg
07/09/2010 - 06:56

I wanted also to know if there is a reason you used the var keyword insted of DataRow

reply

The Reddest
07/09/2010 - 07:29

The only reason to do it is to save some typing. I don't like the var keyword if the type is not obvious by looking at the line of code. In this case, it's pretty obvious since it's being set to the return value of NewRow. I don't like seeing the following example:

var myVar = SomeFunction();

When reading the code, I have no idea what type myVar is.

reply

Renatius
07/15/2010 - 16:30

I have a problem with the

_PublisherCombo.ItemsSource = new List<string>()  { "Activision", "Ubisoft",
        "Microsoft", "Blizzard", "Nintendo", "Electronic Arts",
        "Take-Two Interactive" };

part. What if I want to include items of an existing array as ItemsSource?

I´ve tried many things. Setting the array itself as source (just wrote the arrayname on the righthandside), or puting the array items into a list an set it as Source and some other things, but nothing works.

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.

Sponsors