2

I'd like to display a table of items in WPF. In general that's not that hard, except that:

  • I know, which columns I want to display only at runtime (user in a way defines them), and
  • I'd like to avoid using codebehind: the XAML, where I want to display the table is inside a DataTemplate, which is inside another DataTemplate etc. Introducing CodeBehind there will be problematic (though I will do that if I have no other options).

I though of using WPF's DataGrid, which is smart enough to extract column names in runtime (depending on properties of collection items), but I know which columns I want to display only at runtime - I would have to create object with specific properties at runtime, what is also problematic (if possible).

Also, I don't really need to use specifically the DataGrid, as this will be simple table of strings for preview only - it may be as well displayed, say, inside Grid with ItemsControl - I just need to provide a view with columns and rows.

So the questions are:

  • How to customize displayed columns in DataGrid only from ViewModel (DataContext) and XAML?, or
  • How to display tabular for preview data when columns are known at runtime only?
3
  • So what exactly will you be binding your itemsource to? Different object types? A csv? Commented Jan 29, 2018 at 20:15
  • Simply, a list of list of strings. Commented Jan 29, 2018 at 21:01
  • Possible duplicate of How do I bind a WPF DataGrid to a variable number of columns?. My preferred approach however is to use DataTable Commented Jan 30, 2018 at 7:13

1 Answer 1

1

Since it seems like there is interest for this problem and I found solution myself, here it goes (attached properties rulez!)

For clarity, I created model classes to wrap list of list of strings:

public class TableDataRow
{
    public TableDataRow(List<string> cells)
    {
        Cells = cells;
    }

    public List<string> Cells { get; }
}

public class TableData
{
    public TableData(List<string> columnHeaders, List<TableDataRow> rows)
    {
        for (int i = 0; i < rows.Count; i++)
            if (rows[i].Cells.Count != columnHeaders.Count)
                throw new ArgumentException(nameof(rows));

        ColumnHeaders = columnHeaders;
        Rows = rows;
    }

    public List<string> ColumnHeaders { get; }
    public List<TableDataRow> Rows { get; }
}

Now we define the attached property:

public static class DataGridHelper
{
    private static void TableDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var dataGrid = d as DataGrid;
        var tableData = e.NewValue as TableData;
        if (dataGrid != null && tableData != null)
        {
            dataGrid.Columns.Clear();
            for (int i = 0; i < tableData.ColumnHeaders.Count; i++)
            {
                DataGridColumn column = new DataGridTextColumn
                {
                    Binding = new Binding($"Cells[{i}]"),
                    Header = tableData.ColumnHeaders[i]
                };
                dataGrid.Columns.Add(column);
            }

            dataGrid.ItemsSource = tableData.Rows;
        }
    }

    public static TableData GetTableData(DependencyObject obj)
    {
        return (TableData)obj.GetValue(TableDataProperty);
    }

    public static void SetTableData(DependencyObject obj, TableData value)
    {
        obj.SetValue(TableDataProperty, value);
    }

    // Using a DependencyProperty as the backing store for TableData.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TableDataProperty =
        DependencyProperty.RegisterAttached("TableData", 
            typeof(TableData), 
            typeof(DataGridHelper), 
            new PropertyMetadata(null, TableDataChanged));
}

The usage is trivial:

(...)
xmlns:h="clr-namespace:<namespace-of-DataGridHelper>"
(...)    

<DataGrid AutoGenerateColumns="False" h:DataGridHelper.TableData="{Binding ResultData}" />

Obviously DataContext must publish TableData via ResultData. Don't forget about AutoGenerateColumns, otherwise you'll receive additional column "Cells".

Sign up to request clarification or add additional context in comments.

1 Comment

Can you please add calling code. attaching tabledata to resultdata.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.