37

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.

Rahul Tripathi
  • 168,305
  • 31
  • 280
  • 331
Wr4thon
  • 554
  • 1
  • 5
  • 11

4 Answers4

64

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.

Community
  • 1
  • 1
McGarnagle
  • 101,349
  • 31
  • 229
  • 260
  • 7
    I found the DataTable approach to be the easiest and most efficient solution. – Nick Falco Mar 18 '15 at 22:33
  • 4
    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. – Jesse Dec 16 '16 at 03:40
  • 1
    what if we want to edit and update values in the same data grid – Shelly Aug 21 '17 at 06:22
  • 2
    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? – Denise Skidmore Sep 17 '18 at 17:28
  • Tough one, I don't know off hand. Call InvalidateVisual maybe? – McGarnagle Sep 17 '18 at 17:41
  • 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? – vargonian Mar 01 '19 at 19:47
  • 3rd approach is discussed [here](https://social.msdn.microsoft.com/Forums/en-US/177aef94-62c5-438d-a4a9-8834390559ad/wpf-datagrid-and-itypedlist-propertydescriptorgetvalue-is-not-called?forum=wpf). – Honza Vojtěch Sep 17 '21 at 18:11
  • The 2nd approach didn't work for me, the data was loaded but the data grid displayed nothing. After that, I created the data grid columns manually (and I don't want this way) and it worked, so what should I do here? – Younes Belouche Dec 21 '22 at 08:24
10

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.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
FC Joe H
  • 101
  • 1
  • 4
2

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"/>
Jim Simson
  • 2,774
  • 3
  • 22
  • 30
Bedi
  • 55
  • 6
  • 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. – Wr4thon Nov 28 '18 at 11:17
  • Would be very interested if u can make a custom Control out of it. That would help me a lot. – Bedi Nov 28 '18 at 12:22
0

If you are not required to show that into a single big DataGrid(table) then you could have a DataGrid with id,image,name,Description and when one of the records is selected on that DataGrid then you show/refresh a ListBox with the name of images that are related to that selected record

Mauricio Gracia Gutierrez
  • 10,288
  • 6
  • 68
  • 99
  • 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 – Wr4thon Aug 27 '13 at 12:08