UPDATE: DataGridComboBoxColumn has been updated from CTP to V1.  See the post here for the updates to the DataGridComboBoxColumn as well as an updated sample.  

If you haven’t already, you can download the binaries and source for the DataGrid v1 here

DataGridComboBoxColumn is a column that deserves some special attention.  What makes it a little unique is how it hooks up to a source list of items and how the current SelectedItem maps back to the data source.  Recall from my previous post on DataGridColumns, you will generally set the DataFieldBinding to map a property of the data source to the cells of a column,

<dg:DataGridTextColumn DataFieldBinding="{Binding Path=FirstName}" />

 

Also recall that each column type has a separate UIElement when in an editing and non-editing state.  For a DataGridTextColumn, it generates a TextBlock for non-editing and TextBox for editing.  When each is generated the DataFieldBinding is mapped to the UIElement.  No problems.  For a DataGridComboBoxColumn, the default template for a non-editing state is a TextBlock and the default template for an editing state is a ComboBox.  Mapping the DataFieldBinding to the TextBlock works but will not work so easily with the ComboBox as it’s an ItemsControl that is defined by a list and not a single object.  With that in mind, some special APIs were introduced to DataGridComboBoxColumn.

DataGridComboBoxColumn APIs

These are the two public APIs specific to DataGridComboBoxColumn:    

public class DataGridComboBoxColumn : DataGridBoundColumn

{
  public
static readonly DependencyProperty DataFieldTargetProperty;

  public static readonly DependencyProperty ItemsSourceProperty;

  public ComboBoxDataFieldTarget DataFieldTarget { get; set; }

  public IEnumerable ItemsSource { get; set; }
}

 

ItemsSource, which is pretty straightforward, is the ItemsSource set on the generated ComboBox for an editing state.  DataFieldTarget does two things.  First, it represents the selection value from the ComboBox.  This value can be the ComboBox.SelectedItem, ComboBox.SelectedValue, or ComboBox.Text.  Second, it is the mapping between the DataFieldBinding and the ComboBox.  It is in this second part that allows you to update the ComboBox’s SelectedItem and changes will reflect back to the DataGrid’s data source.

Example

Using the Northwind Database as an example (I’ve also included instructions in the sample), let’s say I want to display an editable Orders table and for the CustomerID foreign key column, I want to display a drop down list of possible choices.  The choices will be all the CustomerID values from the Customer table.  For the implementation, I’m going to auto-generate the columns so I can also show an example of how to customize columns when they are auto-generated.

Here is how I am populating the DataGrid with the Orders table:   

private void btn_GetOrders_Click(object sender, RoutedEventArgs e)

{
  _orderDataSet = DBAccess.GetOrders();

  if (_orderDataSet != null)

  {
    _orderDateTable = _orderDataSet.Tables["Orders"];

    DataGrid_Standard.ItemsSource = _orderDateTable.DefaultView;
  }
}

 

I have a separate utility class to retrieve the DataSet which I call DBAccess.  Then I get the DataTable and set the default view to the DataGrid.  This will trigger the DataGridColumns to be auto-generated.  For auto-generated columns you have access to two events, AutoGeneratingColumn and AutoGeneratedColumns, where you can customize the behavior of the columns.  In my case I want to create a DataGridComboBoxColumn for the CustomerID field of the Orders table.  Here is a possible implementation:       

private void DataGrid_Standard_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)

{
  if
(e.PropertyName == "CustomerID")

  {
    DataGridComboBoxColumn column = new DataGridComboBoxColumn();

    column.DataFieldBinding = new Binding("CustomerID");

    column.DataFieldTarget = ComboBoxDataFieldTarget.SelectedValue;

    column.ItemsSource = DBAccess.GetCustomers().Tables["Customers"].DefaultView;

    column.EditingElementStyle =   (Style)this.RootGrid.FindResource("CustomerFKStyle");

    e.Column = column;
  }
}

 

<Style x:Key="CustomerFKStyle" TargetType="ComboBox">

  <Setter Property="SelectedValuePath" Value="CustomerID" />

  <Setter Property="ItemTemplate">

    <Setter.Value>

      <DataTemplate>

        <TextBlock Text="{Binding Path=CustomerID}" />

      </DataTemplate>

    </Setter.Value>

  </Setter>

</Style>           

 

I set the DataFieldBinding to the CustomerID as that is the value that I want to represent in each cell of that column.  I set the column.ItemsSource to the Customers table.   Each item in the ComboBox.ItemsSource represents a record in the Customers table but I really just want to show is its CustomerID.  I can do this by setting its SelectedValuePath and ItemTemplate to CustomerID which I do in the CustomerFKStyle.  Remember that a DataGridComboBoxColumn generates a ComboBox only in the editing state so I have to set this style on column.EditingElementStyle.  Lastly, I set column.DataFieldTarget to SelectedValue as this is value that will update the CustomerID property of my Orders table.

You can check out the full sample here.  Note: I didn’t write the code to persist changes back to the database.  

Also check out, WPF DataGrid: Working with DataGridComboBoxColumn (Part 2) for more info on DataGridComboBoxColumns. 

More Related Material:

DataGrid Intro 

Dissecting the Visual Layout

Other Samples:

ScrollViewer with ToolTip

Custom sorting, column selection, single-click editing

Tri-state sorting