Search code examples
c#xamluwpbindingitemsource

Filling DataGrid using nested lists and dynamic columns


I have a strange data model and I'm trying to generate dynamic columns in a datagrid and bind the items correctly.

I have a list of Row objects that I want to bind to a DataGrid and display using simple DataGridTextColumn.

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

My objective is to grab a list of columns from the first row and build my grid columns while setting up the bindings. I'm having trouble figuring out the correct way to bind the data at 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") }
        });
    }
}

And in my viewmodel I have:

public ObservableCollection<Row> CurrentRows

And a Row object looks like this:

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

    public event PropertyChangedEventHandler PropertyChanged;
}

And finally a RowValue object looks like this:

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

    public event PropertyChangedEventHandler PropertyChanged;
}

Column looks like this:

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

ColumnValidation looks like this:

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;
}

Solution

  • My solution ended up being to build a DataTable using my list of column definitions and row data. It's a pretty dirty solution but it suits my purposes. I'm not doing any editing or saving from the grid, it is simply for displaying data. The editing occurs in a dialog elsewhere.

    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;
    }