WPF Datagrid Combobox Column

Recently I was building a WPF desktop application which required the use of a datagrid for the purpose of displaying a table of data. All was well (and simple!) until it came to using a combobox in a couple of the columns of the datagrid. In my case, the requirement was that a combobox would allow the user, for each row, in the street address cell, to select from a list of dynamically populated values, as the right street address to use for that cell in that row, when the data would be exported.  Sounds easy…right? Well it didn’t apear so at first because the DataGridComboBoxColumn which I had added to the datagrid in Blend wasn’t showing up in the column at all when I would load data into it. As I began to do ye ‘ole Google-it dance to see who else out there had encountered the same roadblock to fulfilling a similar requirement, I quickly began to realize it was not as obvious an interface feature as I had first thought it must be. So I decided I would record this on my blog, to perhaps help others who might find themselves searching for something similar.

Adding data to the data grid

First, I was loading data into the grid “directly” in a loop like so:

this.dataGridResults.Items.Add(dataItems[i]);

Using binding expressions to map property values to cells

The data (data[i]) was for each row added, a user-defined type in my solution, whose properties contained the data that belonged in each cell of a row, and all of the columns in the datagrid were bound with the same type of binding expression:

ItemsSource="{Binding SomeDataItemProperty}" 

or:

Header="{Binding SomeDataItemProperty}"

DataGridComboBoxColumn v.s. DataGridTemplateColumn

The property which was supposed to be providing data to the combobox was an array of type: string (string[]). Of course, this binding did not work for the combobox columns, in these, there was just a blank cell, no combobox, nothing. So this brought me to my first problem: nothing was showing at all when I added a DataGridComboBoxColumn in Blend!  Getting past that required going directly to the underlying XAML rather than the “design” view in Blend, and replacing the DataGridComboBoxColumn I started with (put there by Blend) with a DataGridTemplateColumn:

<DataGridTemplateColumn Header="Street Address">
 <DataGridTemplateColumn.CellTemplate>
  <DataTemplate>
   <ComboBox ItemsSource="{Binding StreetAddresses}" BorderBrush="{x:Null}" Background="#00FFFFFF"/>
  </DataTemplate>
 </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Now the data grid was actually showing combo boxes in the column…and the array of strings (StreetAddresses property of the data item type) was showing up in the comboboxes!

Setting the selected item

Next up was making sure the item in the combobox which matched a property of the row data item would be selected when the data grid was populated with data. This required the “SelectedItem” property of the DataGridTemplateColumn’s ComboBox to be set as follows:

<ComboBox ItemsSource="{Binding StreetAddresses}" SelectedItem="{Binding StreetAddress, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" BorderBrush="{x:Null}" Background="#00FFFFFF"/>

The Binding was to the property StreetAddress, set as Mode=TwoWay, so that the value of the property on the row’s data item would get updated when a user changed the selection in the combobox for that row. The UpdateSourceTrigger=PropertyChanged was required to cause the “TwoWay” update to change that row’s data item property value. Here is that data item type, see if you can correlate the terms in the binding expression to those within the type:

        public class MyRowItem : INotifyPropertyChanged
        {
            private string streetAddress;
            public string StreetAddress
            {
                get { return this.streetAddress; }
                set
                {
                    if (this.streetAddress != value)
                    {
                        this.streetAddress = value;
                        FirePropertyChanged("StreetAddress");
                    }
                }
            }
            private List<string> streetAddresses;
            public List<string> StreetAddresses
            {
                get { return this.streetAddresses; }
                set
                {
                    if (this.streetAddresses != value)
                    {
                        this.streetAddresses = value;
                    }
                }
            }
 
            [field: NonSerialized]
            public event PropertyChangedEventHandler PropertyChanged;
 
            private void FirePropertyChanged(string propertyName)
            {
                if (this.PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
 
            public MyRowItem()
            {
                this.streetAddress = string.Empty;
                this.StreetAddress = string.Empty;
                this.StreetAddresses = new List<string>();
            }
        }
    }

Allowing the column to be sorted by whatever is selected in each combobox

Now that the items were showing up, the correct item was selected in each combobox according to the value of the StreetAddress property, next up was allowing the user to sort the column according to each combobox’s selected item, which required the use of the SortMemberPath and CanUserSort attributes on the DataGridTemplateColumn tag:

<DataGridTemplateColumn Header="Street Address" SortMemberPath="StreetAddress" CanUserSort="true" >

Including the selected item in the output using ClipboardContentBinding

The datagrid was being output to a CSV file in my case, so all that remained at this point was to make sure that whatever was selected in each combobox, was what would show up in that cell in the CSV file that was the applications output. The ClipboardContentBinding attribute of the DataGridTemplateColumn tag had to be set using a binding expression:

<DataGridTemplateColumn Header="Street Address" ClipboardContentBinding="{Binding StreetAddress}" SortMemberPath="StreetAddress" CanUserSort="true" >

Output datagrid data to CSV

Finally, for those who may be wondering, here is how easy it was to have the datagrid output to a CSV file:

                this.dataGridResults.SelectAllCells();
                this.dataGridResults.ClipboardCopyMode = (includeHeaders) ? DataGridClipboardCopyMode.IncludeHeader : DataGridClipboardCopyMode.ExcludeHeader;
                ApplicationCommands.Copy.Execute(null, this.dataGridResults);
                this.dataGridResults.UnselectAllCells();
                string result = Clipboard.GetData(DataFormats.CommaSeparatedValue).ToString();
                Clipboard.Clear();
                File.AppendAllText(filePath, result);

..and voila! It was not so hard after all, but also not very obvious if you didn’t already know. Now you do.

Happy coding!

5 thoughts on “WPF Datagrid Combobox Column

  1. Hi. I see that you don’t update your site too often. I know
    that writing posts is boring and time consuming.
    But did you know that there is a tool that allows you to create new articles
    using existing content (from article directories or other pages from your niche)?
    And it does it very well. The new articles are high quality and pass the copyscape test.
    You should try miftolo’s tools

  2. Thank you for a great column. Even though I am looking at it 3 years + later, it is still relevant. What does the entire XAML for the DataGrid look like? What was the DataContext?

  3. hi, please me .. how to solve datagrid combobox selection changed using XAML(wpf)???

    using database data how to populate row details using combobox slection in datagrid ???

    for example

    part no part description unit quantity

    see above datagrid , use only one combobox , part no columns is combobox, if combobox selection of partno (drop down) is selectedd one part number, this part no details is displayed same row ( ie part descrption , unit , quntity)… all data is in database table

Leave a Reply

Your email address will not be published.