WPF DataGrid: Working with DataGridComboBoxColumns CTP (Part 2)

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.

Previously I started a post on DataGridComboBoxColumns where I introduced the APIs specific to it and showed an example of how to use it. Well, one thing I didn't show that seems to be a common ask is how to mask what is displayed versus what is actually being updated. Let’s say I have a CustomerID that I want to display as before with a DataGridComboBoxColumn but I want the display values to be a little more user friendly. For the ComboBox (editing state) I can use DisplayMemberBinding for the display value and SelectedValuePath for the actual value but for the TextBlock (non-editing state) I don't have anything that can map the CustomerID to something else. Let’s take a look at a couple solutions.

One solution could be using a converter on the DataFieldBinding to transform the CustomerID to some other alias. Remember though in our example, the overall table (Orders) that the DataGrid is displaying is different that the table that the ComboBox is using (Customers). So in your converter you may need to optimize the calls to get the Customer table. I have excluded that from the example as that is a separate topic of discussion.            

<dg:DataGridComboBoxColumn

        DataFieldBinding="{Binding CustomerID,

                           Converter={StaticResource CustomerConverter},

                         ConverterParameter=ContactName}"

        ItemsSource="{Binding Source={StaticResource customerDataProvider}}"

        Header="CustomerID (ContactName alias)"

        DataFieldTarget="Text">

  <dg:DataGridComboBoxColumn.EditingElementStyle>

    <Style TargetType="ComboBox">

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

      <Setter Property="DisplayMemberPath" Value="ContactName" />

    </Style>

  </dg:DataGridComboBoxColumn.EditingElementStyle>

</dg:DataGridComboBoxColumn>

           

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

{

  string customerID = (string)value;

  DataTable dataTable = DBAccess.GetCustomers().Tables["Customers"];

  foreach (DataRow row in dataTable.Rows)

  {

    if (row["CustomerID"].ToString() == customerID)

      return row[parameter.ToString()];

  }

  return null;

}

The converter is returning a string value and remember that the DataFieldTarget is what maps the ComboBox value back to the DataFieldBinding, so I have set the DataFieldTarget to “Text”. Also, as the DisplayMemberPath and ConverterParameter show, I am using the ContactName as the alias.

Another solution which is very similar to the first one is to use the converter as I did above but with a DataGridTemplateColumn. There really isn’t a big advantage going with a DataGridTemplateColumn over a DataGridComboBoxColumn in this particular example, but it does give you more flexibility on the type of element to display in the non-editing state. Anyway, here is an example:                       

<dg:DataGridTemplateColumn Header="CustomerID (ContactName alias)">

  <dg:DataGridTemplateColumn.CellTemplate>

    <DataTemplate>

      <TextBlock Text="{Binding Path=CustomerID, Mode=OneWay, Converter={StaticResource CustomerConverter}, ConverterParameter=ContactName}" />

    </DataTemplate>

  </dg:DataGridTemplateColumn.CellTemplate>

  <dg:DataGridTemplateColumn.CellEditingTemplate>

    <DataTemplate>

      <ComboBox ItemsSource="{Binding Source={StaticResource customerDataProvider}}"

        SelectedValue="{Binding CustomerID}" SelectedValuePath="CustomerID" DisplayMemberPath="ContactName" />

    </DataTemplate>

  </dg:DataGridTemplateColumn.CellEditingTemplate>

</dg:DataGridTemplateColumn>

One thing to note about this example is what is bound to what. In CellTemplate, the TextBlock is using DataGrid’s DataContext and is bound to the CustoemrID field of the Orders table. In CellEditingTemplate, the ComboBox is using the Customer table as the Source for the ItemsSource but the DataContext is still the DataGrid for everything elese. That means that SelectedValue’s binding to CustomerID is based on the CustomerID of the Orders table and that binding is the one that will update the source.

A third and a little more time consuming approach would be to subclass DataGridComboBoxColumn and add a property for the display. If you take a look at the DataGridHyperlinkColumn it actually has a special API for something like this which is ContentBinding. ContentBinding maps the content it is bound to what gets displayed as the link in the cells of that column. However, there is still a DataFieldBinding which maps to the actual hyperlink that it represents.

Here is a sample for the first two solutions.

 

DataGrid_ComboBoxColumnSamples.zip

Comments

  • Anonymous
    August 28, 2008
    PingBack from http://blogs.msdn.com/vinsibal/archive/2008/08/26/wpf-datagrid-working-with-datagridcomboboxcolumn.aspx

  • Anonymous
    August 29, 2008
    I have really not much enthusiasm for these converter solutions. A combobox in a datagrid is used almost everywhere. It cannot be to discover each time such code constructs, also not for a subclassing solution.  We all heard about Rapid Application Development. In Windows Forms, we use for a combobox 4 properties: DataPropertyName, DataSource, DisplayMember and ValueMember. In WPF we need the same or something similar! (An even “more” Rapid Application Development was possible with Access 15 years ago…) So, since this is just a preview, I hope that the final version of the grid will have these improvements. Could you tell us, if the development goes in this direction? Thanks a lot Peter

  • 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
    August 29, 2008
    Overview The DataGrid uses a set of DataGridColumns to describe how to display its data just like a GridView

  • Anonymous
    September 03, 2008
    Peter, In the current implementation of DataGrid and ComboBox a conversion must take place to go from one table to another.  Because of that, the DataGrid cannot guess how to convert so the app developer must provide a custom implementation.  

  • Anonymous
    September 18, 2008
    Hi Vincent, I am stuck with using comboBox in dataGrid  where my combobox should have list of images and it should be getting from code-behind page of xaml page. So, I have one method say "CablesImagesMethod" that returns System.Windows.Controls.Images. But, I wanna bind the selected value from combobox to "ImageStore" property of collection that is bind to dataGrid. Exactly, like your second example but in my case I need to use Image rather than "TextBlock" (I think so). so my code is like.... <Grid.Resources>        <ObjectDataProvider x:Key="imageDataProvider" ObjectType="{x:Type local:CablesSegmentsPage}" MethodName="CablesImagesMethod" />        <UI1:ImageStoreConverter x:Key="ImageConverter"/>      </Grid.Resources> <dg:DataGridTemplateColumn>            <dg:DataGridTemplateColumn.CellTemplate>              <DataTemplate>                <Image Source="{Binding Path=ImageStore, Converter={StaticResource ImageConverter}}" Width="50" Height="25"/>              </DataTemplate>            </dg:DataGridTemplateColumn.CellTemplate>            <dg:DataGridTemplateColumn.CellEditingTemplate>              <DataTemplate>                <ComboBox ItemsSource="{Binding Source={StaticResource imageDataProvider}}"                          SelectedValue="{Binding Path=ImageStore, Converter={StaticResource ImageConverter}}" SelectedValuePath="ImageStore" Width="50" />                </DataTemplate>            </dg:DataGridTemplateColumn.CellEditingTemplate>          </dg:DataGridTemplateColumn> In result, it is displaying column as an image with converting and displaying correct image from "ImageStore" property of collection. But when I click on image, it becomes empty combobox and after when I select or click on another cell it goes back to image with displaying correct image. So, my question is why it is not displaying or getting anything in combobox? for testing purpose, I have one combobox outside the DataGrid and code for that is like... <ComboBox Grid.Row="1" ItemsSource="{Binding ElementName=m_CablesSegmentsPage, Path=CablesImages}"/> and it is getting all correct images and displaying. so , I tried using that way in binding ItemSource with elementName and Path, but still it doesn't work for DataGrid. So, In conclusion, How can I populate a ComboBox inside DataGrid with collection of Images that is comming from a property (or method) of code-behind page?

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

  • Anonymous
    September 20, 2008
    Hardik, Is your ImageConverter being called at all for the ComboBox binding?  Also, for the binding on the Image, the Mode should be OneWay.  I don't think that would be the reason why the images are not showing up in the ComboBox though.  Could you also post a snippet of the class for the return value of the method CableImages.

  • Anonymous
    September 21, 2008
    Hi Vincent, I have put that ImageConverter in ComboBox for try and error purpose, it don't think so it does a metter, because I am not using any converter while binding with combobox outside the dataGrid and it displays the images. my class snippet is like.. in Xaml page: <UserControl x:Class="MyNameSpace.CablesSegmentsPage" xmlns:local="clr-namespace:MyNameSpace" ....... x:Name="m_CablesSegmentsPage"> in code-behind page (C#).. namespace MyNameSpace {     public partial class CablesSegmentsPage        {           public Collection<Image> CablesImagesMethod() {                        //code for getting images in cableImages......... return cablesImages; }        } } give me some idea, if I am doing something wrong.

  • Anonymous
    September 22, 2008
    Hardik, I did something along these lines and got it to work: <dg:DataGridTemplateColumn.CellEditingTemplate>                        <DataTemplate>                            <ComboBox ItemsSource="{Binding Source={StaticResource ImageList}}"                                                                      SelectedItem="{Binding Path=Picture, Converter={StaticResource ImageConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">                                                           </ComboBox>                        </DataTemplate>                    </dg:DataGridTemplateColumn.CellEditingTemplate> public class ImageConverter : IValueConverter    {        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)        {            return new Image { Source = new BitmapImage(new Uri("pack://application:,,,/" + (string)value)) };        }        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)        {            Image image = value as Image;            return (image.Source as BitmapImage).UriSource.AbsolutePath;        }    }

  • Anonymous
    September 22, 2008
    Hi Vincent, Is it possible to send me your whole sample than it will be more clear for me? because i can't figure out that how do you define StaticResource ImageList. Thanks in advance...

  • Anonymous
    September 22, 2008
    Hi Vincent, I got my combobox working with exactly the same stuff i was doing before, but only problem was my method wasn't static. thanks a lot.....

  • Anonymous
    September 23, 2008
    Hi Vincent, I have one more questions, when I click on ComboBox, it is not remebering (Displaying) the currentItem. Is it a normal or something that I am not doing? thnaks

  • Anonymous
    October 02, 2008
    Here is one more solution without using Converter. The idea is to use ComboBox in CellTemplate with attribute IsEnabled="False": <dg:DataGridTemplateColumn Header="CustomerID (ContactName alias)">    <dg:DataGridTemplateColumn.CellTemplate>        <DataTemplate>            <ComboBox IsEnabled="False" ItemsSource="{Binding Source={StaticResource customerDataProvider}}" SelectedValue="{Binding Path=CustomerID}" SelectedValuePath="CustomerID" DisplayMemberPath="ContactName" />        </DataTemplate>    </dg:DataGridTemplateColumn.CellTemplate>    <dg:DataGridTemplateColumn.CellEditingTemplate>        <DataTemplate>            <ComboBox ItemsSource="{Binding Source={StaticResource customerDataProvider}}" SelectedValue="{Binding Path=CustomerID}" SelectedValuePath="CustomerID" DisplayMemberPath="ContactName" />        </DataTemplate>    </dg:DataGridTemplateColumn.CellEditingTemplate> </dg:DataGridTemplateColumn>

  • Anonymous
    October 02, 2008
    Vitaly, That is a good solution and at the time of writing the post I didn't think about that.  In fact, the DataGridComboBoxColumn has already been updated in the final release with a somewhat similar solution.  Thanks for your input!

  • Anonymous
    October 31, 2008
    Hardik, I know it's a pretty late reply, but are you having the same ComboBox issue with the v1 version?

  • Anonymous
    November 05, 2008
    Can you please tell me how autocomplete and autosuggest feature can be added to the combobox column?

  • Anonymous
    November 05, 2008
    Sachet, Try setting the EditingElementStyle in a DataGridComboBoxColumn, <dg:DataGridComboBoxColumn.EditingElementStyle>                        <Style TargetType="ComboBox">                            <Setter Property="IsEditable" Value="True" />                        </Style>                    </dg:DataGridComboBoxColumn.EditingElementStyle>

  • Anonymous
    April 30, 2009
    The comment has been removed

  • Anonymous
    April 30, 2009
    Ivan, See this post, http://blogs.msdn.com/vinsibal/archive/2008/12/17/wpf-datagrid-dynamically-updating-datagridcomboboxcolumn.aspx.

  • Anonymous
    January 12, 2010
    Hi VinSibal, Please have a look at this link, I had posted my question with sample code there

  1. http://stackoverflow.com/questions/2006434/setting-wpf-datagrid-combobox-value-to-its-respective-textbox
  2. http://stackoverflow.com/questions/1974251/problem-while-binding-datagridcomboboxcolumn-in-datagrid
  • Anonymous
    March 02, 2010
    Dear Vinsibal, First off, thank you very much for your blogs. I've got my app to work just like yours.
  • But what if we make from Order and Customer classes. And Order does not contain a CustomerID property, but an entire instance of a Customer class. So we have a observable collection of Order classes with a Customer property in each Order.
  • Next to that we have a seperate observable collection of Customer classes with all the customers in it. Just like in your example we display the Orders in a DataGrid and we make the Customer column a DataGridComboBoxColumn which displays all the Customers in edit mode, but displays the Customer.Name (for instance) in 'normal' display mode which currently is referenced by the Order in question. If I select a Customer from the list in the combobox I want it to update the Customer property of the Order. How do I do this?