DataGridTemplateColumns, AutoGenerateColumns=true and binding to a DataTable

百般思念 提交于 2019-12-01 21:54:31

问题


I'm struggling with a confluence of problems.

  1. I have a dynamic data set which I manually assemble into a DataTable.
  2. I have to auto generate the columns as the data is not static.
  3. 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!