0

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?

mcalex
  • 6,628
  • 5
  • 50
  • 80
  • Where do you set `ItemsSource` of the `DataGridComboBoxColumn`? [This](https://stackoverflow.com/questions/5409259/binding-itemssource-of-a-comboboxcolumn-in-wpf-datagrid) topic can help you. – Maxim Jul 13 '17 at 05:02
  • That's in the xaml. I didn't include it because I had tried a few different things in there. I have tried setting ItemsSource to {Binding Manufacturer}, and to the List / ObservableCollection I created in code-behind. I generally get this error: `System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element` – mcalex Jul 13 '17 at 05:07
  • Show how you set `ItemsSource` and also show property in view-model that set as items source. – Maxim Jul 13 '17 at 05:23
  • @Maxim Updated. um, it's not proper mvvm, so view-model == code-behind. – mcalex Jul 13 '17 at 05:52
  • `{StaticResource Manufacturer}` What it is? Where `Manufacturer` defined? Anyway if `ManufacturerList` defined right in the code-behind of window this should work: `ItemsSource="{Binding Path=DataContext.ManufacturerList, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Mode=OneWay}"` – Maxim Jul 13 '17 at 05:57
  • Sorry for typo, yes, it's {StaticResource MaufacturerList}. – mcalex Jul 13 '17 at 06:06
  • In this case you do absolutely wrong thing since `MaufacturerList` is not a resource. Use `ItemsSource` binding I've provided above, it should work. And please take some time to learn WPF basics. – Maxim Jul 13 '17 at 06:12
  • I changed the ItemsSource as you suggested. This came back with `System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=DataContext.ManufacturerList; DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=36678780); target property is 'ItemsSource' (type 'IEnumerable')` My 'window' is a UserControl, so I changed AncestorType to: x:Type UserControl, but got the same error. – mcalex Jul 13 '17 at 06:35
  • Show where you set `DataContext` of the `UserControl`. Also try to use approach from the answer to this question: [Binding ItemsSource of a ComboBoxColumn in WPF DataGrid](https://stackoverflow.com/questions/5409259/binding-itemssource-of-a-comboboxcolumn-in-wpf-datagrid) – Maxim Jul 13 '17 at 07:51
  • I have a `` where DataCollectionSource.xaml holds ``. The model namespace points to the EF generated model. – mcalex Jul 13 '17 at 08:00

2 Answers2

1

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.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • I had set up the list as a property in some of my previous attempts, then I tried to make it static then I figured a property can't be static and changed it to a field. Anyhoo. I followed your suggestion and received: System.Windows.Data Error: 40 : BindingExpression path error: 'ManufacturerList' property not found on 'object' ''CollectionViewSource' (HashCode=1121399)'. BindingExpression:Path=DataContext.ManufacturerList; DataItem='ProductsTabData' (Name='ucProductsTabItem'); target element is 'TextBlockComboBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable') – mcalex Jul 14 '17 at 04:07
  • If it helps, my app is a `Window` holding a `TabControl`, holding multiple `UserControl`s (that I'm calling 'TabItem'). The TabItem resources include a `ResourceDictionary` containing multiple `CollectionViewSource`s. This is where the main data for the app comes from. I'm wondering if I should add another CollectionViewSource to capture the Manufacturers? – mcalex Jul 14 '17 at 04:11
  • Thank you mm8. I didn't realise the existing data context would cause a problem as everywhere was saying that the DataGridComboBoxColumn was not in the tree and so I figured that part was unimportant. I do apologise. Thank you. Solved. – mcalex Jul 14 '17 at 07:45
0

You have to define a user control resource like bellow

<UserControl.Resources>
    <CollectionViewSource x:Key="UserTypeList" Source="{Binding DomainUserTypes}" /> </UserControl.Resources>
  1. Bind the Data grid Combo column as below

    DataGridComboBoxColumn Header="User Type" ItemsSource="{Binding Source={StaticResource UserTypeList}}

  2. DomainType will be the view model property.

    DomainUserTypes = new ObservableCollection();

just populate the View model property with data and test, it will work.

Shanjee
  • 491
  • 5
  • 16