WPF DataGrid binding with ObservableCollection

datagridmvvmobservablecollectionwpfxaml

I'm trying to bind a ObservableCollection with rows of data to my datagrid.
I'm aiming on making the datagrid update itself (if data is added), it didn't worked when using a DataTable.
I tested this approach and it works just fine, my datagrid ist bound to ResultTable property and the property names are the column names.

public class MainWindowViewModel
{
    public MainWindowViewModel()
    {
        ResultTable = new ObservableCollection<Row>();
        ResultTable.Add(new Row() { Name = "Peter", Age = "21", Sex = "Male" });
        ResultTable.Add(new Row() { Name = "Sara", Age = "25", Sex = "Female" });
        ResultTable.Add(new Row() { Name = "Mike", Age = "28", Sex = "Male" });
    }

    public ObservableCollection<Row> ResultTable { get; set; }
}

public class Row
{
    public string Name { get; set; }
    public string Age { get; set; }
    public string Sex { get; set; }
}

Now my question is, how can I do this dynamically?
This was my second approach and looks like nothing in the DataGrid…

public class MainWindowViewModel
{
    public MainWindowViewModel()
    {
        ResultTable = new ObservableCollection<Row>();
        var row = new Row();
        row.Columns.Add(new Item() { Name = "Name", Value = "Peter" });
        row.Columns.Add(new Item() { Name = "Age", Value = "21" });
        row.Columns.Add(new Item() { Name = "Sex", Value = "Male" });
        ResultTable.Add(row);
    }

    public ObservableCollection<Row> ResultTable { get; set; }
}

public class Row
{
    public List<Item> Columns = new List<Item>();
}

public class Item
{
    public string Name { get; set; }
    public string Value { get; set; }
}

But now the question is, how can I say the DataGrid that each Name property in columns List is the column header.
And each Value is the value of the row?

This is my xaml (nothing fancy):

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainWindowViewModel x:Name="MainWindowViewModel" />
    </Window.DataContext>
    <Grid>
        <DataGrid ItemsSource="{Binding ResultTable}"
                  AutoGenerateColumns="True" />
    </Grid>
</Window>

Best Answer

Ok so have tested this and works as per your diagram in the comments below.

Row class

public class Row
{
    private ObservableCollection<Item> columns = new ObservableCollection<Item>();

    public Row(params Item[] items)
    {
        foreach (var item in items)
            Columns.Add(item);
    }

    public ObservableCollection<Item> Columns
    {
        get { return columns; }
        set { columns = value; }
    }
}

Item.cs

public class Item 
{
    public Item(string name, string value)
    {
        this.Name = name;
        this.Value = value;
    }
    public string Name { get; set; }
    public string Value { get; set; }     
}

Custom DataGRidBoundColumn

public class CustomBoundColumn : DataGridBoundColumn
{
    public string TemplateName { get; set; }

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        var binding = new Binding(((Binding)Binding).Path.Path);
        binding.Source = dataItem;

        var content = new ContentControl();
        content.ContentTemplate = (DataTemplate)cell.FindResource(TemplateName);
        content.SetBinding(ContentControl.ContentProperty, binding);
        return content;
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        return GenerateElement(cell, dataItem);
    }
}

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public ObservableCollection<Row> ResultTable
    {
        get;
        set;
    }
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
        Loaded += (s, args) =>
            {
                this.ResultTable = new ObservableCollection<Row>();
                ResultTable.Add(new Row(new Item("Name", "Peter"), new Item("Age", "21"), new Item("Sex", "Male")));
                ResultTable.Add(new Row(new Item("Name", "Sara"), new Item("Age", "25"), new Item("Sex", "Female")));
                ResultTable.Add(new Row(new Item("Name", "Mike"), new Item("Age", "28"), new Item("Sex", "Male")));

                var columns = ResultTable.First()
                           .Columns
                           .Select((x, i) => new { Name = x.Name, Value = x.Value, Index = i })
                           .ToArray();

                foreach (var column in columns)
                {
                    var binding = new Binding(string.Format("Columns[{0}].Value", column.Index));
                    data.Columns.Add(new CustomBoundColumn()
                        {
                            Header = column.Name,
                            Binding = binding,
                            TemplateName = "CustomTemplate"
                        });                        
                }
                data.ItemsSource = this.ResultTable;

            };
    }
}

and finally the xaml nice and simple

 <Window.Resources>
    <DataTemplate x:Key="CustomTemplate">
        <Border Padding="3">
            <TextBlock Text="{Binding}" Foreground="Black" />
        </Border>
    </DataTemplate>
</Window.Resources>

    <Grid>
        <DataGrid Name="data"
                  AutoGenerateColumns="False">            
        </DataGrid>
    </Grid>     

This is done in code behind but I imagine you can adjust for your viewmodel. More about the fix can be found Here

Related Topic