Search code examples
c#wpfdatagriddatagridcomboboxcolumn

Binding Related Data in DataGridComboBoxColumn


I have a datagrid that works fine for displaying data, but now I want to use it to edit data. How do I configure a DataGridComboBoxColumn to display the description, but bind to the ID of the related table?

My data comes into the application via EF, but to get correctly filtered data into the DataGrid, I use a DbQuery to create a List<> which is then set as the DataGrid's ItemsSource:

private void OnControlLoaded(object sender, RoutedEventArgs e)
{
  DbQuery<Product> whiteGoodsProductQuery = this.GetWhitegoodsProductsQuery(myEntities);
  List<Product> lstWhiteGoodsProducts = whiteGoodsProductQuery.ToList<Product>();
  dgridWhitegoodProducts.ItemsSource = lstWhiteGoodsProducts;
}

This populates the DataGrid as so:

<DataGrid x:Name="dgridWhitegoodProducts" AutoGenerateColumns="False">
  <DataGrid.Columns>
    <!-- Manufacturer -->
    <DataGridTextColumn Header="Manufacturer" Binding="{Binding Manufacturer.CompanyName}"  />
    <!-- Product -->
    <DataGridTextColumn Header="Name" Binding="{Binding ProductName }" />
    <!-- Description -->
    <DataGridTextColumn Header="Product Description" Binding="{Binding ProductDescription }"/>
    <!-- Length -->
    <DataGridTextColumn Header="Length (mm)" Binding="{Binding Length}"/>
    <!-- Width -->
    <DataGridTextColumn Header="Width (mm)" Binding="{Binding Width}"/>
    <!-- Height -->
    <DataGridTextColumn Header="Height (mm)" Binding="{Binding Height}"/>
    <!-- Price -->
    <DataGridTextColumn Header="Price" Binding="{Binding UnitPrice}"/>
  </DataGrid.Columns>
</DataGrid>

Now, I want to change the Manufacturer DataGridTextColumn into a DataGridComboBoxColumn so that users can select a different manufacturer. When I set up the ComboBox column, I don't get any data, so I'm obviously doing something wrong in the binding.

Here's what my combo box column initially looked like:

<DataGridComboBoxColumn Header="Manufacturer"
    SelectedValueBinding="{Binding ManufacturerID, Mode=TwoWay}" 
    SelectedValuePath="Manufacturer.ID" 
    DisplayMemberPath="Manufacturer.CompanyName" />

Currently it looks like:

<DataGridComboBoxColumn Header="Manufacturer" SelectedValueBinding="{Binding 
    ManufacturerID}" SelectedValuePath="ID" DisplayMemberPath="CompanyName}" 
    ItemsSource="{Binding Source={StaticResource ManufacturerList}" />

With the ItemsSource coming from a static ObservableCollection in the code-behind.

public static ObservableCollection<Manufacturer> ManufacturerList = new 
    ObservableCollection<Manufacturer>(new MyEntities().Manufacturers);

I have tried a number of variations of [TableName.]Field in the SelectedValuePath, SelectedValueBinding etc properties, to no avail.

The next thing I tried was to create a list List in code-behind that the ComboBoxColumn could bind to, but when I set the List as the ItemsSource, I get: System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=ManufacturerList; DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=14427695); target property is 'ItemsSource' (type 'IEnumerable').

Changing that List to an ObservableCollection didn't work either, nor did making it static.

Can anyone see what I am doing wrong and which way I should go from here?


Solution

  • You can only bind to public properties and ManufacturerList is a field.

    If you define this as a non-static property in the code-behind of your UserControl:

    public ObservableCollection<Manufacturer> ManufacturerList { get; } = new ObservableCollection<Manufacturer>(new MyEntities().Manufacturers);
    

    ...you could bind to it like this:

    <DataGridComboBoxColumn Header="Manufacturer" SelectedValueBinding="{Binding ManufacturerID, Mode=TwoWay}" 
                            SelectedValuePath="ID" 
                            DisplayMemberPath="CompanyName">
        <DataGridComboBoxColumn.ElementStyle>
            <Style TargetType="ComboBox">
                <Setter Property="ItemsSource" Value="{Binding DataContext.ManufacturerList, RelativeSource={RelativeSource AncestorType=UserControl}}" />
            </Style>
        </DataGridComboBoxColumn.ElementStyle>
        <DataGridComboBoxColumn.EditingElementStyle>
            <Style TargetType="ComboBox">
                <Setter Property="ItemsSource" Value="{Binding DataContext.ManufacturerList, RelativeSource={RelativeSource AncestorType=UserControl}}" />
            </Style>
        </DataGridComboBoxColumn.EditingElementStyle>
    </DataGridComboBoxColumn>
    

    ID and CompanyName are supposed to be public properties of the Manufacturer class and ManufacturerID is a public property of the Product class.

    The reason why you need to set the ItemsSource property in the styles is that a DataGridComboBoxColumn is not a visual element that gets added to the element tree and therefore it has no visual ancestors.

    Edit:

    So apparently the DataContext of your UserControl is a CollectionViewSource. This is an example of why you should always provide a reproducible sample of your issue when asking a question.

    Anyway, you could define another CollectionViewSource in your UserControl and set the Source of this one to your collection of Manufacturer objects and bind the column like this:

    <DataGridComboBoxColumn ... ItemsSource="{Binding Source={StaticResource ManufacturerList}}" />
    

    ...where ManufacturerList is the x:Key of the CollectionViewSource.