Skip to content

Excedrin headache #3.5.40128.1: Using combo boxes with the WPF DataGrid

If you’re working with the WPF DataGrid (January 2009 release, version 3.5.40128.1) and want to display a column of combo boxes (e.g. to select a value from a list of options) you may be in for a headache, especially if you want the available options to be supplied from the binding context. For example say you are displaying an editable table of orders, and each order is for a particular quantity of a particular product, and the products are selectable using combo boxes.

Here is a solution, the main element of which came from a posting on the Codeplex web site from someone called XIU for which I’m grateful. I thought I would add some more detail in case anyone else has this problem and wants a more cut and pasty solution.

Say you have a class called Product, each instance of which has an ID and a Description, and a class called Order, each instance of which has a Quantity and a Product ID (to be used like a foreign key to designate the order’s product).

These classes must implement the INotifyPropertyChanged interface. Here are the first few lines of each:

public class Product : INotifyPropertyChanged
{
  public int ID { ... }
  public string Description { ... }
  ...
}
 
public class Order : INotifyPropertyChanged
{
  public int ProductID { ... }
  public int Quantity { ... }
  ...
}

Create a simple ViewModel (i.e. binding context) and set the DataContext of the main window to an instance of this object:

namespace ComboBoxDemo
{
  public class ViewModel
  {
    public ObservableCollection<Order> Orders { get; set; }
    public ObservableCollection<Product> Products { get; set; }
 
    public ViewModel()
    {
      Products = new ObservableCollection<Product>();
      Products.Add(new Product(100, "Widget"));
      Products.Add(new Product(101, "Sprocket"));
      Products.Add(new Product(102, "Gadget"));
 
      Orders = new ObservableCollection<Order>();
      Orders.Add(new Order(102, 15));
      Orders.Add(new Order(102, 32));
      Orders.Add(new Order(100, 42));
    }
  }
}

Create a helper class using XIU’s solution:

namespace ComboBoxDemo
{
  public class DataGridComboBoxColumnWithBindingHack : DataGridComboBoxColumn
  {
    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
      FrameworkElement element = base.GenerateEditingElement(cell, dataItem);
      CopyItemsSource(element);
      return element;
    }
 
    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
      FrameworkElement element = base.GenerateElement(cell, dataItem);
      CopyItemsSource(element);
      return element;
    }
 
    private void CopyItemsSource(FrameworkElement element)
    {
      BindingOperations.SetBinding(element, ComboBox.ItemsSourceProperty,
        BindingOperations.GetBinding(this, ComboBox.ItemsSourceProperty));
    }
  }
}

This is a variant of the DataGridComboBoxColumn class which handles binding differently to allow the ItemsSource property to have access to the binding context. Now put it all together with the following XAML:

<Window x:Class="ComboBoxDemo.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:wpf="http://schemas.microsoft.com/wpf/2008/toolkit"
  xmlns:local="clr-namespace:ComboBoxDemo"
  Title="Window1" Height="300" Width="300">
  <wpf:DataGrid ItemsSource="{Binding Orders}" AutoGenerateColumns="False">
    <wpf:DataGrid.Columns>
      <wpf:DataGridTextColumn Header="Quantity" Binding="{Binding Quantity}" />
      <local:DataGridComboBoxColumnWithBindingHack
        Header="Product"
        SelectedValueBinding="{Binding ProductID}"
        SelectedValuePath="ID"
        DisplayMemberPath="Description"
        ItemsSource="{Binding RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}, Path=DataContext.Products}" />
    </wpf:DataGrid.Columns>
  </wpf:DataGrid>
</Window>

The SelectedValueBinding attribute indicates the Order property containing the foreign key designating the product (i.e. which value should be edited by the combo box). The SelectedValuePath attribute indicates which property within the product object contains the product ID, and the DisplayMemberPath attribute indicates which property should actually be displayed in the grid. Finally the ItemsSource attribute indicates how to get the list of products by navigating to the parent window object and looking up the list of products from the parent window’s binding context.

Here is the complete project source as a downloadable zip file.

I hope this saves someone some time, and that it’s easier to do all of this in future versions of the WPF Toolkit. If anyone has a better solution to this problem, please let me know.

{ 11 } Comments

  1. Daniel Simon | February 19, 2009 at 6:45 pm | Permalink

    The problem here seems to be that the DataGridColumn doesn’t get placed in the UserControl’s tree, so the natural instinct for solving this problem – using an Ancestor RelativeSource binding – doesn’t work. I think this is pretty plainly a bug.

    I’ve solved this problem in my application a bit more directly, by setting the DataGridComboBoxColumn’s ItemsSource binding in the code-behind of the control in which the DataGrid is contained. Not clean M-V-VM-wise, but very easily understood, I think.

  2. Florian | June 30, 2009 at 6:06 am | Permalink

    Thanks a lot !!! It works great.
    The bug has not been fixed in the latest release of the WPF Toolkit (June 2009, 25th), so I’ll keep using your solution which is MVVM compliant.

    Keep up the good work :-)

  3. Michael Butler | July 30, 2009 at 8:06 am | Permalink

    Thanks for this. This problem has caused me a lot of pain over the last day or so, glad I finally stumbled upon your solution.

  4. Vince Plaza | August 21, 2009 at 7:25 pm | Permalink

    Elegant work-around that you clearly documented. My only suggestion is to include the “using” statements in the code block. I picked them up from the zip file, though, so not a big deal.

  5. FixFix | October 13, 2009 at 8:24 pm | Permalink

    You are my god!!! thanks a lot…I was getting crazy with this trick

  6. Jacek Sieka | October 15, 2009 at 8:33 am | Permalink

    Here’s another way without the extra class.

  7. Bernhard König | July 30, 2010 at 10:16 am | Permalink

    Or, just use a template colum instead of a combobox column and put a Combobox in there manually. Works fine with a relative ancestor source.

  8. Wim Bokkers | August 12, 2010 at 3:51 am | Permalink

    Even the .NET 4 version still contains this bug!

  9. Stephen M | November 18, 2010 at 8:24 pm | Permalink

    Great solution. I’ve been going nuts looking for this. Thanks!

  10. Jorge G. | May 5, 2012 at 8:34 pm | Permalink

    Question!

    I’ve implented this on my project but the DropDown List is not displaying the description for each element on it (I only got the reference as ComboBoxDemo.Product). However, if I select 1 of those elements, I got the description right on the cell.

    Is this a bug? How can I fix it?

  11. Don B | September 17, 2012 at 1:59 pm | Permalink

    I’ll add my thanks here as well. This is a really confusing area of WPF and your example was just what I needed after struggling (guessing) on my own for a few hours.

Post a Comment

Your email is never published nor shared. Required fields are marked *