Filling a Datagrid with dynamic Columns

49,170

Solution 1

There are at least three ways of doing this:

  1. Modify the DataGrid's columns manually from code-behind
  2. Use a DataTable as the ItemsSource *
  3. Use a CustomTypeDescriptor

    *recommended for simplicity


1st approach: use code-behind to generate the DataGrid's columns at runtime. This is simple to implement, but maybe feels a bit hackish, especially if you're using MVVM. So you'd have your DataGrid with fixed columns:

<DataGrid x:Name="grid">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding id}" Header="id" />
        <DataGridTextColumn Binding="{Binding image}" Header="image" />
    </DataGrid.Columns>
</DataGrid>

When you have your "Names" ready, then modify the grid by adding/removing columns, eg:

// add new columns to the data grid
void AddColumns(string[] newColumnNames)
{
    foreach (string name in newColumnNames)
    {
        grid.Columns.Add(new DataGridTextColumn { 
            // bind to a dictionary property
            Binding = new Binding("Custom[" + name + "]"), 
            Header = name 
        });
    }
}

You'll want to create a wrapper class, which should contain the original class, plus a dictionary to contain the custom properties. Let's say your main row class is "User", then you'd want a wrapper class something like this:

public class CustomUser : User
{
    public Dictionary<string, object> Custom { get; set; }

    public CustomUser() : base()
    {
        Custom = new Dictionary<string, object>();
    }
}

Populate the ItemsSource with a collection of this new "CustomUser" class:

void PopulateRows(User[] users, Dictionary<string, object>[] customProps)
{
    var customUsers = users.Select((user, index) => new CustomUser {
        Custom = customProps[index];
    });
    grid.ItemsSource = customUsers;
}

So tying it together, for example:

var newColumnNames = new string[] { "Name1", "Name2" };
var users = new User[] { new User { id="First User" } };
var newProps = new Dictionary<string, object>[] {
    new Dictionary<string, object> { 
        "Name1", "First Name of First User",
        "Name2", "Second Name of First User",
    },
};
AddColumns(newColumnNames);
PopulateRows(users, newProps);

2nd approach: use a DataTable. This makes use of the custom-type infrastructure under the hood, but is easier to use. Just bind the DataGrid's ItemsSource to a DataTable.DefaultView property:

<DataGrid ItemsSource="{Binding Data.DefaultView}" AutoGenerateColumns="True" />

Then you can define the columns however you like, eg:

Data = new DataTable();

// create "fixed" columns
Data.Columns.Add("id");
Data.Columns.Add("image");

// create custom columns
Data.Columns.Add("Name1");
Data.Columns.Add("Name2");

// add one row as an object array
Data.Rows.Add(new object[] { 123, "image.png", "Foo", "Bar" });

3rd approach: make use of the extensibility of .Net's type system. Specifically, use a CustomTypeDescriptor. This allows you to create a custom type at runtime; which in turn enables you to tell the DataGrid that your type has the properties "Name1", "Name2", ... "NameN", or whatever others you want. See here for a simple example of this approach.

Solution 2

2nd approach: use a DataTable. This makes use of the custom-type infrastructure under the hood, but is easier to use. Just bind the DataGrid's ItemsSource to a DataTable.DefaultView property:

This almost worked but instead of binding to the DataTable.DefaultView property property I created a property of type DataView and bound to that.

<DataGrid ItemsSource="{Binding DataView, Mode=TwoWay}" AutoGenerateColumns="True" />

This allows the binding to be two way, binding to the DataTable.DefaultView cannot be a TwoWay binding. In the View Model

    public DataView DataView
    {
        get { return _dataView; }
        set
        {
            _dataView = value;
            OnPropertyChanged("DataView");
        }
    }

With this setup I could not only define the columns dynamically when the View Model is initialized, but could update and change the data table dynamically at any time. In using the approach as defined by McGarnagle above, the view schema was not refreshing when the DataTable was updated with a new data source.

Solution 3

I'm currently using another approach. I am not sure if it is right to do it like this, but it works. I made a small sample.

Keep in mind that for this to work, every entry in the Datagrid needs to have the same dynamic columns, making it a bit less flexible. But if you have entries with different amounts of columns in each entry, then a Datagrid is probably the wrong Approach anyways.

These are my classes:

 public class Person
    {
        public ObservableCollection<Activity> Hobbys { get; set; }
        public string Name { get; set; }
    }
 public class Activity
    {
        public string Name { get; set; }
    }

And this is the Code Behind:

public MainWindow()
        {
            InitializeComponent();
            DataContext = this;

            ObservableCollection<Activity> hobbys = new ObservableCollection<Activity>();
            hobbys.Add(new Activity() { Name = "Soccer" });
            hobbys.Add(new Activity() { Name = "Basketball" });

            Community = new ObservableCollection<Person>();
            Community.Add(new Person() { Name = "James", Hobbys = hobbys });
            Community.Add(new Person() { Name = "Carl", Hobbys = hobbys });
            Community.Add(new Person() { Name = "Homer", Hobbys = hobbys });

            MyGrid.Columns.Add(new DataGridTextColumn() { Header = "Name", Binding = new Binding("Name") });    //Static Column
            int x = 0;
            foreach (Activity act in Community[0].Hobbys)  //Create the dynamic columns
            {
                MyGrid.Columns.Add(new DataGridTextColumn() { Header = "Activity", Binding = new Binding("Hobbys["+x+"].Name") });
                x++;
            }

        }

And in the .XAML is simply:

  <DataGrid Name="MyGrid" ItemsSource="{Binding Community}" AutoGenerateColumns="False"/>
Share:
49,170
Wr4thon
Author by

Wr4thon

foo

Updated on April 10, 2021

Comments

  • Wr4thon
    Wr4thon about 3 years

    I have an Datagrid which needs to get filled dynamicly.

    The tablelayout is like:

    id | image | name | Description | Name-1 | Name-N
    

    The first 4 columns are static the others are dynamic. The User should be able to add as many users as he wants.

    I try to compare data of multiple users by putting them next to each other in the table.

    Right now I have an Listbox whitch containes the Names of the dynamic generated Columns and an method that filles the static columns. I also can load the datas for each User. now I need to merge them to one big Table.

    The main Problem is now: How to put the "Userdata" and the static content in one datagrid.

  • Wr4thon
    Wr4thon over 10 years
    Well I dont think this will solve my Problem. I have one big Table that containes some Data (No matter whitch) now I need to compare multiple Names with the records. So for each record i need "N" more Colummns to directly compare two or more Persons. e. g. : Record 1: 1;tmp.png;Foo;bar;succeeded;not succeeded Record 2: 2;tmp2.png;Foo1;bar2;not succeeded;succeeded
  • Nick Falco
    Nick Falco about 9 years
    I found the DataTable approach to be the easiest and most efficient solution.
  • Jesse
    Jesse over 7 years
    Has anyone successfully auto-generated columns in the DataGrid using method 3? I already have a custom type descriptor defined, I can bind to its properties just fine, but auto-generating columns is not working.
  • Shelly
    Shelly over 6 years
    what if we want to edit and update values in the same data grid
  • Denise Skidmore
    Denise Skidmore over 5 years
    This only seems to work if the data columns are added before the control is initialized. If you change the columns later how do you get that to be reflected in the grid?
  • McGarnagle
    McGarnagle over 5 years
    Tough one, I don't know off hand. Call InvalidateVisual maybe?
  • Wr4thon
    Wr4thon over 5 years
    I will test this, as soon as i have time, but it looks like it could work. I don't like, that you have to access the DataGrid in the code behind, but since most other solutions do that as well, it does not seem like there is a way arount that. I guess it's possible though to make a custom control of it. I might try implementing one and post it here, when I have time to get to it.
  • Bedi
    Bedi over 5 years
    Would be very interested if u can make a custom Control out of it. That would help me a lot.
  • vargonian
    vargonian about 5 years
    This is probably a very basic question, but how do I have the View listen for changes in the ViewModel (new column information updated, for example, which requires new column bindings generated) so that it can programmatically re-generate these bindings?
  • klasyc
    klasyc over 2 years
    3rd approach is discussed here.