91

I have two simple Model classes and a ViewModel...

public class GridItem
{
    public string Name { get; set; }
    public int CompanyID { get; set; }
}

public class CompanyItem
{
    public int ID { get; set; }
    public string Name { get; set; }
}

public class ViewModel
{
    public ViewModel()
    {
        GridItems = new ObservableCollection<GridItem>() {
            new GridItem() { Name = "Jim", CompanyID = 1 } };

        CompanyItems = new ObservableCollection<CompanyItem>() {
            new CompanyItem() { ID = 1, Name = "Company 1" },
            new CompanyItem() { ID = 2, Name = "Company 2" } };
    }

    public ObservableCollection<GridItem> GridItems { get; set; }
    public ObservableCollection<CompanyItem> CompanyItems { get; set; }
}

...and a simple Window:

<Window x:Class="DataGridComboBoxColumnApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GridItems}" >
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Name}" />
                <DataGridComboBoxColumn ItemsSource="{Binding CompanyItems}"
                                    DisplayMemberPath="Name"
                                    SelectedValuePath="ID"
                                    SelectedValueBinding="{Binding CompanyID}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

The ViewModel is set to the MainWindow's DataContext in App.xaml.cs:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        MainWindow window = new MainWindow();
        ViewModel viewModel = new ViewModel();

        window.DataContext = viewModel;
        window.Show();
    }
}

As you can see I set the ItemsSource of the DataGrid to the GridItems collection of the ViewModel. This part works, the single Grid line with Name "Jim" is displayed.

I also want to set the ItemsSource of the ComboBox in every row to the CompanyItems collection of the ViewModel. This part does not work: The ComboBox remains empty and in the Debugger Output Window I see an error message:

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=CompanyItems; DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=28633162); target property is 'ItemsSource' (type 'IEnumerable')

I believe that WPF expects CompanyItems to be a property of GridItem which is not the case, and that's the reason why the binding fails.

I've already tried to work with a RelativeSource and AncestorType like so:

<DataGridComboBoxColumn ItemsSource="{Binding CompanyItems, 
    RelativeSource={RelativeSource Mode=FindAncestor,
                                   AncestorType={x:Type Window}}}"
                        DisplayMemberPath="Name"
                        SelectedValuePath="ID"
                        SelectedValueBinding="{Binding CompanyID}" />

But that gives me another error in the debugger output:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=CompanyItems; DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=1150788); target property is 'ItemsSource' (type 'IEnumerable')

Question: How can I bind the ItemsSource of the DataGridComboBoxColumn to the CompanyItems collection of the ViewModel? Is it possible at all?

Thank you for help in advance!

Slauma
  • 175,098
  • 59
  • 401
  • 420
  • Folks, please refrain from getting the community to write your code. Post generalized questions to get feedback and solution options. These answers are easily adapted to many circumstances. Answers that write your code are not as helpful. – Jim Broiles Aug 18 '22 at 16:05

8 Answers8

137

Pls, check if DataGridComboBoxColumn xaml below would work for you:

<DataGridComboBoxColumn 
    SelectedValueBinding="{Binding CompanyID}" 
    DisplayMemberPath="Name" 
    SelectedValuePath="ID">

    <DataGridComboBoxColumn.ElementStyle>
        <Style TargetType="{x:Type ComboBox}">
            <Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
        </Style>
    </DataGridComboBoxColumn.ElementStyle>
    <DataGridComboBoxColumn.EditingElementStyle>
        <Style TargetType="{x:Type ComboBox}">
            <Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
        </Style>
    </DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>

Here you can find another solution for the problem you're facing: Using combo boxes with the WPF DataGrid

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
serge_gubenko
  • 20,186
  • 2
  • 61
  • 64
  • 5
    Hell, this works !!! If I only could understand why? And why not the original code with the changes Rachel recommended? Anyway, thank you very much! – Slauma Mar 23 '11 at 19:43
  • 1
    I believe you can find the explanation here: http://wpf.codeplex.com/workitem/8153?ProjectName=wpf (see comments) – serge_gubenko Mar 23 '11 at 19:53
  • 1
    They seem to have decided to turn this bug ("We have filed a bug in our internal database to be fixed in a future release.") into a feature. Take a look at my own answer in this thread: The problem has been solved by documentation, a strong indication that this will never be changed. – Slauma Mar 23 '11 at 20:27
  • 1
    +1 for the http://joemorrison.org/blog/2009/02/17/excedrin-headache-35401281-using-combo-boxes-with-the-wpf-datagrid/ link. that solved my issue. Sucks, ~5 hours and I realized I already had this type in my project for something else we were doing :( Its always a learning process. – TravisWhidden Jun 28 '11 at 00:01
  • Doesn't work for me. The EditingElementStyle seems to work, but for some reason once I add ElementStyle I get ComboBoxes that don't populate anything (instead of the value from DisplayMemberPath), and it won't toggle back to the EditingElementStyle when I click. – William May 11 '13 at 00:19
  • have a look here on codeplex: https://wpf.codeplex.com/workitem/8153 and vote for this bug. – juFo Jan 02 '14 at 14:01
  • Thanks @serge_gubenko Your solution fixed the issue but its very slow when I moved between combo items do you know why ? – Mohammed Thabet May 23 '15 at 22:56
  • It worked for me too using the suggested solution on the link you provided (Joe on Computing site). For the record, I was trying to bind an ICollectionViewSource to a DataGridComboBoxColumn and were experiencing the same problem. – Igor Kondrasovas Nov 05 '15 at 11:45
  • there is one problem(i don't mean result of this answer, but of the whole solution as well): the state of CompanyItems are shared. if we have a requirement, to allow each row select some of CompanyItems(binding IsChecked property for example), one row's change can affect all rows. how to solve this problem? – Lei Yang Jul 31 '20 at 08:49
50

The documentation on MSDN about the ItemsSource of the DataGridComboBoxColumn says that only static resources, static code or inline collections of combobox items can be bound to the ItemsSource:

To populate the drop-down list, first set the ItemsSource property for the ComboBox by using one of the following options:

  • A static resource. For more information, see StaticResource Markup Extension.
  • An x:Static code entity. For more information, see x:Static Markup Extension.
  • An inline collection of ComboBoxItem types.

Binding to a DataContext's property is not possible if I understand that correctly.

And indeed: When I make CompanyItems a static property in ViewModel ...

public static ObservableCollection<CompanyItem> CompanyItems { get; set; }

... add the namespace where the ViewModel is located to the window ...

xmlns:vm="clr-namespace:DataGridComboBoxColumnApp"

... and change the binding to ...

<DataGridComboBoxColumn
    ItemsSource="{Binding Source={x:Static vm:ViewModel.CompanyItems}}" 
    DisplayMemberPath="Name"
    SelectedValuePath="ID"
    SelectedValueBinding="{Binding CompanyID}" />

... then it works. But having the ItemsSource as a static property might be sometimes OK, but it is not always what I want.

Slauma
  • 175,098
  • 59
  • 401
  • 420
47

The correct solution seems to be:

<Window.Resources>
    <CollectionViewSource x:Key="ItemsCVS" Source="{Binding MyItems}" />
</Window.Resources>
<!-- ... -->
<DataGrid ItemsSource="{Binding MyRecords}">
    <DataGridComboBoxColumn Header="Column With Predefined Values"
                            ItemsSource="{Binding Source={StaticResource ItemsCVS}}"
                            SelectedValueBinding="{Binding MyItemId}"
                            SelectedValuePath="Id"
                            DisplayMemberPath="StatusCode" />
</DataGrid>

The layout above works perfectly fine for me, and should work for others. This design choice also makes sense, though it isn't very well explained anywhere. But if you have a data column with predefined values, those values typically don't change during run-time. So creating a CollectionViewSource and initializing the data once makes sense. It also gets rid of the longer bindings to find an ancestor and bind on it's data context (which always felt wrong to me).

I am leaving this here for anyone else who struggled with this binding, and wondered if there was a better way (As this page is obviously still coming up in search results, that's how I got here).

Adam Becker
  • 573
  • 4
  • 5
  • 2
    Though arguably a fine answer, it is perhaps **abstracted** from the OP's question. Your `MyItems` would lead to a compile error if used with the OP's code –  Feb 04 '15 at 04:30
24

I realize this question is over a year old, but I just stumbled across it in dealing with a similar problem and thought I would share another potential solution in case it might help a future traveler (or myself, when I forget this later and find myself flopping around on StackOverflow between screams and throwings of the nearest object on my desk).

In my case I was able to get the effect I wanted by using a DataGridTemplateColumn instead of a DataGridComboBoxColumn, a la the following snippet. [caveat: I'm using .NET 4.0, and what I've been reading leads me to believe the DataGrid has done a lot of evolving, so YMMV if using earlier version]

<DataGridTemplateColumn Header="Identifier_TEMPLATED">
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <ComboBox IsEditable="False" 
                Text="{Binding ComponentIdentifier,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                ItemsSource="{Binding Path=ApplicableIdentifiers, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding ComponentIdentifier}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Rick Riensche
  • 1,050
  • 1
  • 12
  • 25
  • After struggling with the first couple of answers, I tried this and it also worked for me too. Thanks. – coson Jun 27 '13 at 17:51
8

RookieRick is right, using DataGridTemplateColumn instead of DataGridComboBoxColumn gives a much simpler XAML.

Moreover, putting the CompanyItem list directly accessible from the GridItem allows you to get rid of the RelativeSource.

IMHO, this give you a very clean solution.

XAML:

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GridItems}" >
    <DataGrid.Resources>
        <DataTemplate x:Key="CompanyDisplayTemplate" DataType="vm:GridItem">
            <TextBlock Text="{Binding Company}" />
        </DataTemplate>
        <DataTemplate x:Key="CompanyEditingTemplate" DataType="vm:GridItem">
            <ComboBox SelectedItem="{Binding Company}" ItemsSource="{Binding CompanyList}" />
        </DataTemplate>
    </DataGrid.Resources>
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Name}" />
        <DataGridTemplateColumn CellTemplate="{StaticResource CompanyDisplayTemplate}"
                                CellEditingTemplate="{StaticResource CompanyEditingTemplate}" />
    </DataGrid.Columns>
</DataGrid>

View model:

public class GridItem
{
    public string Name { get; set; }
    public CompanyItem Company { get; set; }
    public IEnumerable<CompanyItem> CompanyList { get; set; }
}

public class CompanyItem
{
    public int ID { get; set; }
    public string Name { get; set; }

    public override string ToString() { return Name; }
}

public class ViewModel
{
    readonly ObservableCollection<CompanyItem> companies;

    public ViewModel()
    {
        companies = new ObservableCollection<CompanyItem>{
            new CompanyItem { ID = 1, Name = "Company 1" },
            new CompanyItem { ID = 2, Name = "Company 2" }
        };

        GridItems = new ObservableCollection<GridItem> {
            new GridItem { Name = "Jim", Company = companies[0], CompanyList = companies}
        };
    }

    public ObservableCollection<GridItem> GridItems { get; set; }
}
Benoit Blanchon
  • 13,364
  • 4
  • 73
  • 81
4

Your ComboBox is trying to bind to bind to GridItem[x].CompanyItems, which doesn't exist.

Your RelativeBinding is close, however it needs to bind to DataContext.CompanyItems because Window.CompanyItems does not exist

Rachel
  • 130,264
  • 66
  • 304
  • 490
  • Thanks for reply! I've tried that (replaced `CompanyItems` by `DataContext.CompanyItems` in the last XAML snippet in my question) but it gives me the same error in the debugger output. – Slauma Mar 23 '11 at 17:51
  • 1
    @Slauma I'm not sure then, it should work. The only thing unusual I see with the XAML you have is the Mode=FindAncestor and I usually omit that. Have you tried giving your root window a Name and referencing it by name in your binding instead of using RelativeSource? `{Binding ElementName=RootWindow, Path=DataContext.CompanyItems}` – Rachel Mar 23 '11 at 18:50
  • Have tried both things (omitted Mode=FindAncestor and changed the binding to a named element), but it doesn't work. Strange that this way works for you. I've created this simple test application to drag the problem out of my application into a very simple context. I don't know what I could make wrong, the code you see in the question is the full application (created from the WPF project template in VS2010), there is nothing more around this code. – Slauma Mar 23 '11 at 19:36
2

the bast way i use i bind the textblock and combobox to same property and this property should support notifyPropertyChanged.

i used relativeresource to bind to parent view datacontext which is usercontrol to go up datagrid level in binding because in this case the datagrid will search in object that you used in datagrid.itemsource

<DataGridTemplateColumn Header="your_columnName">
     <DataGridTemplateColumn.CellTemplate>
          <DataTemplate>
             <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.SelectedUnit.Name, Mode=TwoWay}" />
           </DataTemplate>
     </DataGridTemplateColumn.CellTemplate>
     <DataGridTemplateColumn.CellEditingTemplate>
           <DataTemplate>
            <ComboBox DisplayMemberPath="Name"
                      IsEditable="True"
                      ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.UnitLookupCollection}"
                       SelectedItem="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.SelectedUnit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                      SelectedValue="{Binding UnitId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                      SelectedValuePath="Id" />
            </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
Hisham
  • 1,279
  • 1
  • 17
  • 23
2

This is working for me:


<DataGridTemplateColumn Width="*" Header="Block Names">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox
                VerticalContentAlignment="Center"
                ItemsSource="{Binding DataContext.LayerNames,
                RelativeSource={RelativeSource Findancestor,
                AncestorType={x:Type Window}}}"
                SelectedItem="{Binding LayerName, Mode=TwoWay,
                UpdateSourceTrigger=PropertyChanged}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Toni
  • 1,555
  • 4
  • 15
  • 23
ChuongHo
  • 33
  • 5