Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I dynamically generate columns in a WPF DataGrid?

I am attempting to display the results of a query in a WPF datagrid. The ItemsSource type I am binding to is IEnumerable<dynamic>. As the fields returned are not determined until runtime I don't know the type of the data until the query is evaluated. Each "row" is returned as an ExpandoObject with dynamic properties representing the fields.

It was my hope that AutoGenerateColumns (like below) would be able to generate columns from an ExpandoObject like it does with a static type but it does not appear to.

<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Results}"/> 

Is there anyway to do this declaratively or do I have to hook in imperatively with some C#?

EDIT

Ok this will get me the correct columns:

// ExpandoObject implements IDictionary<string,object>  IEnumerable<IDictionary<string, object>> rows = dataGrid1.ItemsSource.OfType<IDictionary<string, object>>(); IEnumerable<string> columns = rows.SelectMany(d => d.Keys).Distinct(StringComparer.OrdinalIgnoreCase); foreach (string s in columns)     dataGrid1.Columns.Add(new DataGridTextColumn { Header = s }); 

So now just need to figure out how to bind the columns to the IDictionary values.

like image 536
dkackman Avatar asked Dec 30 '09 23:12

dkackman


People also ask

What is the difference between grid and DataGrid in WPF?

A Grid is a control for laying out other controls on the form (or page). A DataGrid is a control for displaying tabular data as read from a database for example.

Can user sort columns WPF?

WPF DataGrid (SfDataGrid) allows you to sort the data against one or more columns either in ascending or descending order. The sorting can be performed by clicking a column header. You can enable/disable the sorting for all the columns in DataGrid by using DataGrid. AllowSorting property.

Can user add rows DataGrid WPF?

WPF DataGrid (SfDataGrid) provides built-in row called AddNewRow. It allows user to add a new row to underlying collection.

What is Data Grid in WPF?

More Detail. A DataGrid is a control that displays data in a customizable grid. It provides a flexible way to display a collection of data in rows and columns. The hierarchical inheritance of DataGrid class is as follows −


2 Answers

Ultimately I needed to do two things:

  1. Generate the columns manually from the list of properties returned by the query
  2. Set up a DataBinding object

After that the built-in data binding kicked in and worked fine and didn't seem to have any issue getting the property values out of the ExpandoObject.

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Results}" /> 

and

// Since there is no guarantee that all the ExpandoObjects have the  // same set of properties, get the complete list of distinct property names // - this represents the list of columns var rows = dataGrid1.ItemsSource.OfType<IDictionary<string, object>>(); var columns = rows.SelectMany(d => d.Keys).Distinct(StringComparer.OrdinalIgnoreCase);  foreach (string text in columns) {     // now set up a column and binding for each property     var column = new DataGridTextColumn      {         Header = text,         Binding = new Binding(text)     };      dataGrid1.Columns.Add(column); } 
like image 145
dkackman Avatar answered Oct 02 '22 12:10

dkackman


The problem here is that the clr will create columns for the ExpandoObject itself - but there is no guarantee that a group of ExpandoObjects share the same properties between each other, no rule for the engine to know which columns need to be created.

Perhaps something like Linq anonymous types would work better for you. I don't know what kind of a datagrid you are using, but binding should should be identical for all of them. Here is a simple example for the telerik datagrid.
link to telerik forums

This isn't actually truly dynamic, the types need to be known at compile time - but this is an easy way of setting something like this at runtime.

If you truly have no idea what kind of fields you will be displaying the problem gets a little more hairy. Possible solutions are:

  • Creating a type mapping at runtime by using Reflection.Emit, I think it's possible to create a generic value converter that would accept your query results, create a new type (and maintain a cached list), and return a list of objects. Creating a new dynamic type would follow the same algorithm as you already use for creating the ExpandoObjects

    MSDN on Reflection.Emit
    An old but useful article on codeproject

  • Using Dynamic Linq - this is probably the simpler faster way to do it.
    Using Dynamic Linq
    Getting around anonymous type headaches with dynamic linq

With dynamic linq you can create anonymous types using a string at runtime - which you can assemble from the results of your query. Example usage from the second link:

var orders = db.Orders.Where("OrderDate > @0", DateTime.Now.AddDays(-30)).Select("new(OrderID, OrderDate)"); 

In any case, the basic idea is to somehow set the itemgrid to a collection of objects whose shared public properties can be found by reflection.

like image 27
Egor Avatar answered Oct 02 '22 14:10

Egor