I'm struggling with a confluence of problems.
Although I thought it would be easy, the ComboBox cannot see the DataItem in the DataView, rather it tries to bind to the DataView directly.
I've put together a sample project here:
https://github.com/5flags/DataGridBindingIssue
Now, it's obviously contrived to demonstrate the issue. I can't change the data structure at this point, so any solution must be done in the XAML.
To see the problems, use Snoop (or equivalent) to see the binding errors on the ComboBoxes.
The DataGrid is set up like so:
<DataGrid AutoGenerateColumns="True" AutoGeneratingColumn="DataGrid_OnAutoGeneratingColumn" CanUserAddRows="False" x:Name="TheDataGrid" ItemsSource="{Binding Data}">
<DataGrid.Resources>
<DataTemplate x:Key="dataItemCellTemplate">
<ComboBox SelectedValue="{Binding Path=SelectedOption, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Options}"/>
</DataTemplate>
</DataGrid.Resources>
</DataGrid>
And the event handler for the autogeneration is:
private void DataGrid_OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyType == typeof(string))
{
var col = new DataGridTextColumn {Binding = new Binding(e.PropertyName), Header = e.PropertyName};
e.Column = col;
}
else if (e.PropertyType == typeof(DataItem))
{
var col = new DataGridTemplateColumn
{
CellTemplate = (DataTemplate) TheDataGrid.FindResource("dataItemCellTemplate"),
CellEditingTemplate = (DataTemplate)TheDataGrid.FindResource("dataItemCellTemplate"),
Header = e.PropertyName
};
e.Column = col;
}
}
The binding error on the combo is:
System.Windows.Data Error: 40 : BindingExpression path error: 'Options' property not found on 'object' ''DataRowView' (HashCode=22264221)'. BindingExpression:Path=Options; DataItem='DataRowView' (HashCode=22264221); target element is 'ComboBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
System.Windows.Data Error: 40 : BindingExpression path error: 'SelectedOption' property not found on 'object' ''DataRowView' (HashCode=22264221)'. BindingExpression:Path=SelectedOption; DataItem='DataRowView' (HashCode=22264221); target element is 'ComboBox' (Name=''); target property is 'SelectedValue' (type 'Object')
Dusan's answer set me on the right track. Because I don't know the column names until runtime, I have to create the data template at runtime too. It's actually not difficult.
private DataTemplate GetDataTemplate(string columnName)
{
string xaml = "<DataTemplate><ComboBox SelectedValue=\"{Binding Path=[" + columnName +
"].SelectedEnumeratedElementItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"" +
" ItemsSource=\"{Binding Path=[" + columnName +
"].Items}\" DisplayMemberPath=\"Name\"/></DataTemplate>";
var sr = new MemoryStream(Encoding.ASCII.GetBytes(xaml));
var pc = new ParserContext();
pc.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
pc.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
var datatemplate = (DataTemplate)XamlReader.Load(sr, pc);
return datatemplate;
}