Condividi tramite


WPF DataGrid: Working with DataGridComboBoxColumn CTP

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

DataGrid_ComboBoxColumnSamples.zip

Comments

  • Anonymous
    August 26, 2008
    PingBack from http://hoursfunnywallpaper.cn/?p=3186

  • Anonymous
    August 27, 2008
    Sharepoint SharePoint Pod Show Episode 3 is Live [Via: Rob Foster ] WPF WPF DataGrid: Working with...

  • Anonymous
    August 27, 2008
    Link Listing - August 26, 2008

  • Anonymous
    August 27, 2008
    Great post and I must say that the DataGrid is looking very good so far! But, I have a problem. I have a DataGrid bound to a DataTable. One column, CategoryId is a foreign key to a table consisting of categories. In the DataGrid that column is a DataGridComboBoxColumn bound to another DataTable containing all the categories. <DataGridComboBoxColumn Header="Category" ItemsSource="{Binding Source={StaticResource Categories}}" DataFieldTarget="SelectedValue"        DataFieldBinding="{Binding CategoryId}" EditingElementStyle="{StaticResource CategoryEditStyle}"/> <Style x:Key="CategoryEditStyle" TargetType="ComboBox">       <Setter Property="SelectedValuePath" Value="CategoryId" /> <Setter Property="DisplayMemberPath" Value="CategoryDescription" /> </Style> Everything works fine except that I would like to have CategoryDescription as text in the TextBox that is displayed in non edit mode. Instead CategoryId is displayed since that is defined as DataFieldBinding. But I cannot change the DataFieldBinding to CategoryDescription since that would break the connection to the DataGrids boud DataTable. I tried to join in the CategoryDescription in the DataGrids bound DataTable and change the ElementStyle: <Style x:Key="CategoryNormalStyle" TargetType="TextBlock"> <Setter Property="ToolTip" Value="{Binding Path=CategoryDescription}" />        <Setter Property="Text" Value="{Binding Path=CategoryDescription}" /> </Style> The tooltip is displayed correctly but not the text (I guess the text property gets owerwritten?) Is there anyway to accomplish this?

  • Anonymous
    August 27, 2008
    This is a hackneyed, horrible solution to something that should be simple to accomplish.  It currently requires WAY too many lines of code to do this simple binding.

  • Anonymous
    August 28, 2008
    Previously I started a post on DataGridComboBoxColumns where I introduced the APIs specific to it and

  • Anonymous
    August 28, 2008
    PMN, I have posted some possible solutions for this here: http://blogs.msdn.com/vinsibal/archive/2008/08/28/wpf-datagrid-working-with-datagridcomboboxcolumns-part-2.aspx Let me know if that works out for you.

  • Anonymous
    August 28, 2008
    Lemonhead, I do understand that it may appear to be a lot of code for this binding and making it easier is one of our goals.  Let's talk about what we are working with and see if there is something that can be done here. So the DataGrid is an ItemsSource like a ListView and has column bindings called DataFieldBinding.  You have a bound list of items and you declare columns with DataFieldBinding on the properties you want to show.  The bindings required there some simple enough. For a ComboBox scenario you have to set the ItemsSource which can come from the DataGrid's data source or some outside data source.  What makes it a little complicated is that a ComboBox's selected item can be different depending on whether you set SelectedValuePath and if you choose to use SelectedItem or SelectedValue as the value that maps back to the TextBlock.  Because of that, DataFieldTarget was introduced so you can specify what you want that value to be.   That's basically all the binding involved for this scenario.  So with that background, I would love to hear your design ideas on how this can be improved.

  • Anonymous
    August 29, 2008
    As you might have heard, .NET Framework 3.5 SP1 and Visual Studio 2008 SP1 are out today! There are a

  • Anonymous
    August 29, 2008
    I’m going to be dissecting and discussing the DataGrid visuals and how they are all assembled together

  • Anonymous
    September 01, 2008
    Thanks vinsibal! I solved it by subclassing DataGridComboBoxColumn and adding a ContentBinding property just as you proposed.

  • Anonymous
    September 16, 2008
    There have been several questions on the WPF CodePlex discussion list relating to styling rows and columns