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!
The correct solution seems to be:
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).
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
Pls, check if DataGridComboBoxColumn xaml below would work for you:
Here you can find another solution for the problem you're facing: Using combo boxes with the WPF DataGrid
RookieRick is right, using
DataGridTemplateColumn
instead ofDataGridComboBoxColumn
gives a much simpler XAML.Moreover, putting the
CompanyItem
list directly accessible from theGridItem
allows you to get rid of theRelativeSource
.IMHO, this give you a very clean solution.
XAML:
View model:
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 existThe documentation on MSDN about the
ItemsSource
of theDataGridComboBoxColumn
says that only static resources, static code or inline collections of combobox items can be bound to theItemsSource
: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 ...... add the namespace where the ViewModel is located to the window ...
... and change the binding to ...
... then it works. But having the ItemsSource as a static property might be sometimes OK, but it is not always what I want.