问题
I'm struggling with a confluence of problems.
- I have a dynamic data set which I manually assemble into a DataTable.
- I have to auto generate the columns as the data is not static.
- I need to bind the ItemsSource of a combo box to an Observable collection defined in each cell.
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')
回答1:
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;
}
回答2:
I dont know how to do this with DataTemplate
from XAML
resources, but it work fine for me with DataTemplate
created in code.
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
System.Windows.Controls.DataGridTemplateColumn templateColumn = new System.Windows.Controls.DataGridTemplateColumn();
templateColumn.Header = e.PropertyName;
DataTemplate template = new DataTemplate();
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(StackPanel));
template.VisualTree = factory;
FrameworkElementFactory childFactory = new FrameworkElementFactory(typeof(TextBox));
childFactory.SetBinding(TextBox.TextProperty, new Binding(e.PropertyName));
factory.AppendChild(childFactory);
templateColumn.CellEditingTemplate = template;
template = new DataTemplate();
factory = new FrameworkElementFactory(typeof(StackPanel));
template.VisualTree = factory;
childFactory = new FrameworkElementFactory(typeof(TextBlock));
childFactory.SetBinding(TextBlock.TextProperty, new Binding(e.PropertyName));
factory.AppendChild(childFactory);
templateColumn.CellTemplate = template;
e.Column = templateColumn;
}
回答3:
Modify your XAML to:
<DataGrid AutoGenerateColumns="True" AutoGeneratingColumn="DataGrid_OnAutoGeneratingColumn" CanUserAddRows="False" x:Name="TheDataGrid" ItemsSource="{Binding Data}">
<DataGrid.Resources>
<DataTemplate x:Key="dataItemCellTemplate">
<ComboBox ItemsSource="{Binding [Option].Options}" SelectedValue="{Binding [Option].SelectedOption, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGrid.Resources>
</DataGrid>
Where [Option]
refers to the column of DataView
in which you store your custom DataItem
objects.
来源:https://stackoverflow.com/questions/20493184/datagridtemplatecolumns-autogeneratecolumns-true-and-binding-to-a-datatable