다음을 통해 공유


WPF ComboBox Binding in DataGrid

One problem for developers new to XAML is binding a combobox in a DataGrid.  
This is a fairly common question in the WPF forum so posters are somehow not finding an article which helps them.
Maybe they find a resource but it's not clear enough how things work - this is quite a complicated and confusing subject.
There are a number of properties which don't work quite how one might expect - Binding the ItemsSource is particularly tricky.
This article will hopefully be found by developers having problems and clear up any confusion.


DataGridComboBoxColumn vs TemplateColumn

Looking at the possible options for a datagrid column the developer wants a combobox in, the obvious choice would seem to be the DataGridComboBoxColumn.
This is the one to go for if you want to display a string in the combobox.
You can supply a template for the item but by the time you do that you may as well be using a DataGridTemplateColumn.
When you want anything other than a string then the column to go with is probably the template column.

Properties to Bind

In the following table, those properties which have Yes in the binding will have a {Binding ....} and those which have No will have a text string like "Id".  This is one of those things which is quite difficult to explain in text but much clearer when you have a working example.   Double check by comparing to the example code. 

ComboBox Property Explanation Binding
ItemsSource Collection of options in ViewModel/Resource Or Resource
SelectedValuePath Id from combo item No
SelectedValueBinding Id of item in Datagrid row Yes
SelectedValue Id of item in Datagrid row Yes
DisplayMemberPath Name of property to show in combo list from combo item No

Note that the properties ending in Name or Path are the text names of your properties as opposed to a Binding.
As if things weren't confusing enough, the DataGridComboBoxColumn has SelectedValueBinding which is a Binding and a regular ComboBox has SelectedValue which will also be a Binding.

Terminology

In the following the ItemsSource of the Datagrid will be referred to as being an ObservableCollection<DataGridRowViewModel>, the ItemsSource of the ComboBox will be referred to as an ObservableCollection<ComboRowViewModel>.

ItemsSource Binding Complications

Unless your ComboBox is using a collection within DataGridRowViewModel then there are complications Binding a combo ItemsSource. 
The combo column look like they're inside the DataGrid so they ought to be in it's datacontext but they only "know" about whatever item they are fed via the ItemsSource of the DataGrid.  This is because the columns aren't in the Visual Tree as one might imagine.
There are two alternative common approaches to resolving the issue.

Use a Resource

You can set the ItemsSource to a StaticResource or DyamicResource which is a Resource within scope of the DataGrid.
The attraction of this approach is the relative simplicity of the XAML you need.  The approach side steps an issue which will become clearer later.

<DataGridComboBoxColumn
    Header="ComboboxColumn"
    ItemsSource="{DynamicResource ColourSource}"
    SelectedValueBinding="{Binding ColourId, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
    SelectedValuePath="Id"
    DisplayMemberPath="ColourString"
    >

There are some complications to creating and filling a DynamicResource ObservableCollection which are covered in this article here.
Exactly what ColourSource is explained there, but it is essentially an ObservableCollection<complexType> which is declared in XAML.
The ItemsSource is otherwise hopefully self explanatory.
SelectedValueBinding is as it's name suggests a Binding to the property in DataGridRowViewModel which will be set as the user chooses an item.
SelectedValuePath is not a binding and is the name of the property  in ComboRowViewModel which will be used to set the SelectedValueBinding property.
DisplayMemberPath is not a binding and is the name of the property in the ComboRowViewModel to be shown.

Bind to the Viewmodel

Most WPF developers will be using MVVM and their Window's DataContext will be set to an instance of a ViewModel. The collection the developer usually wants the user to select from in the combo will be an ObservableCollection<RowViewmodel> which is a Public property of that ViewModel.
There are two aspects to such a binding.

  1. Find the parent Window using RelativeSource
  2. Use the Datacontext

This is probably best explained with an example binding:

  <DataGridTemplateColumn Header="TemplateColumn">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox
                ItemsSource="{Binding DataContext.Colours, RelativeSource={RelativeSource AncestorType=local:MainWindow}}"
                SelectedValuePath="MColour.Id"
                SelectedValue="{Binding ColourId, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
                >
                <ComboBox.ItemTemplate>
                    <DataTemplate>
                         <Rectangle Fill="{Binding ColourBrush}" Height="30" Width="30"/>
                    </DataTemplate>
                </ComboBox.ItemTemplate>
            </ComboBox>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Here , we want to show a coloured rectangle to show the user a representation of what colour the paint they are picking will be.
An explanation of the difference between a combo and template column is probably appropriate here. 
You can actually apply an Itemtemplate to a combo column - so why not use that?
Well the template column is more obvioulsly something which will be templated so it should not surprise any developer to see a template inside it.  A more subtle reason is that template columns cannot be clicked to sort.  This is a positive feature in our case because it avoids a somewhat tricky problem of exactly what you might sort on and how you implement that.  One logical sort of choice would be wavelength to give a rainbow like order.  The simpler approach is to just not offer any sorting.

The ItemsSource is particularly tricky as explained in brief above.  The collection uses a property in the window's viewmodel and this is what the rather strange DataContext., notation is about.  The relativesource goes and finds that parent window by using it's type.  In order to know about that type the XAML has a xmlns reference to our code namespace as local:

xmlns:local="clr-namespace:wpf_ComboBoxColumn"

As the table above explains, the SelectedValuePath is the name of the property out ComboRowViewModel whose value we will be using to identify the object.  If your object originated from a database it probably has an Id you don't want to see and something else you are expecting to see in the items.  In that case it is the Id you use.
The SelectedValue  is a binding to the property DataGridRowViewModel which will change as the user selects an item.
 

It is recommended that the reader downloads and examines the working sample from here.