Заполнение DataGrid с помощью вложенных списков и динамических столбцов

У меня странная модель данных, и я пытаюсь создать динамические столбцы в сетке данных и правильно связать элементы.

У меня есть список объектов Row, которые я хочу привязать к DataGrid и отобразить с помощью простого DataGridTextColumn.

<controls:DataGrid
   Grid.Row="1"
   x:Name="dataGrid"
   ItemsSource="{x:Bind ViewModel.CurrentRows}"

Моя цель — получить список столбцов из первой строки и построить столбцы сетки при настройке привязок. У меня возникли проблемы с определением правильного способа привязки данных в RowValue.value.

public TablePage()
{
    InitializeComponent();

    dataGrid.ItemsSource = ViewModel.CurrentRows;

    foreach (Column column in ViewModel.CurrentRows.FirstOrDefault().Values.Select(x => x.key))
    {
        dataGrid.Columns.Add(new DataGridTextColumn()
        {
            Header = column.ColumnValidation.column_label,
            Binding = new Binding() { Path = new PropertyPath("Values.value") }
        });
    }
}

И в моей модели просмотра у меня есть:

public ObservableCollection<Row> CurrentRows

И объект Row выглядит так:

public class Row: INotifyPropertyChanged
{
    public List<RowValue> Values { get; set; } = new List<RowValue>();

    public event PropertyChangedEventHandler PropertyChanged;
}

И, наконец, объект RowValue выглядит так:

public class RowValue: INotifyPropertyChanged
{
    public Column key { get; set; }
    public string value { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;
}

Колонка выглядит так:

public class Column
{
    public string name;
    public ColumnValidation ColumnValidation;
}

Проверка столбца выглядит следующим образом:

public class ColumnValidation
{
    public string column_label;
    public DataTypeEnum data_type;
    public int width;
    public int decimal_places;
    public int display_order;
    public string calculation_formula;
    public string edit_style;
    public string default_value;
    public decimal? minimum_value;
    public decimal? maximum_value;
    public bool is_column_nullable = false;
    public bool inherit_values = false;
    public bool display_column = false;
    public bool is_editable = false;
    public int column_style;
    public string code_table_name;
    public string code_display_name;
    public string code_data_column_name;
}

person Ian    schedule 20.08.2020    source источник
comment
Здравствуйте, структура вложенной коллекции может не подходить для простой привязки в DataGrid. Возможно, вам придется рассмотреть возможность извлечения column_label и помещения его в класс Row (вам необходимо изменить существующую структуру данных). На самом деле согласно текущей привязке (ObservableCollection‹Row›) можно отображать информацию только на уровне Row, а путь привязки Values.value не будет действовать. Поскольку value является свойством класса RowValue, а не свойством List<RowValue>   -  person Richard Zhang - MSFT    schedule 21.08.2020
comment
Я боялся этого. Я думал, что смогу обойти это, создав умный DataTemplate, но, похоже, нет.   -  person Ian    schedule 21.08.2020
comment
Здравствуйте, шаблон данных, который вы можете вставить в DataGrid, относительно один. Для предустановленных шаблонов столбцов, таких как DataGridTextColumn, необходимо указать фиксированный путь привязки. Если вы хотите использовать DataGridTemplateColumn, хотя он более настраиваемый, он может не удовлетворить ваши потребности в автоматическом создании имен столбцов.   -  person Richard Zhang - MSFT    schedule 24.08.2020


Ответы (1)


Мое решение в конечном итоге заключалось в создании DataTable с использованием моего списка определений столбцов и данных строк. Это довольно грязное решение, но оно подходит для моих целей. Я не делаю никакого редактирования или сохранения из сетки, это просто для отображения данных. Редактирование происходит в другом диалоговом окне.

public TablePage()
{
    InitializeComponent();

    // get list of columns
    List<Column> columns = ViewModel.CurrentTableDefinition.Columns.OrderBy(x => x.ColumnValidation.display_order).ToList();

    // create DataTable
    DataTable dataTable = new DataTable();
    dataTable.Columns.Add(new DataColumn() { ColumnName = "RowObject" }); // add column to store original row object

    // add columns to datagrid and datatable
    for (int i = 0; i < columns.Count; i++)
    {
        // add column to datagrid using the correct header label. bind using index of array.
        dataGrid.Columns.Add(new DataGridTextColumn()
        {
            Header = columns[i].ColumnValidation.column_label,
            Tag = columns[i].name,
            Binding = new Binding() { Path = new PropertyPath("[" + i.ToString() + "]") }
        });

        // add corresponding column to datatable
        dataTable.Columns.Add(new DataColumn() { ColumnName = columns[i].name });
    }

    // iterate through rows of data
    foreach (Row row in ViewModel.CurrentRows)
    {
        // create new datatable row
        DataRow dataRow = dataTable.NewRow();

        // set the original row object 
        dataRow["RowObject"] = row;

        // add data from each column in the row to the datatable row
        for (int i = 0; i < columns.Count; i++)
        {
            // add column value to row
            try
            {
                dataRow[i] = row.Values.Where(x => x.key.name == columns[i].name).FirstOrDefault().value;
            }
            
            catch (Exception ex)
            {
                dataRow[i] = null; // insert null if the table has columns defined for which there is no data in the dataset
            }
        }

        // add datable row to datatable
        dataTable.Rows.Add(dataRow);
    }

    // convert datatable to collection of 'object'
    ObservableCollection<object> collection = new ObservableCollection<object>();

    foreach(DataRow row in dataTable.Rows)
        collection.Add(row.ItemArray);

    // bind to datagrid
    dataGrid.ItemsSource = collection;
}
person Ian    schedule 24.08.2020