views:

1097

answers:

3

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.

+1  A: 
Egor
The data in question come from the tags within mp3 files, so the set is indeed not consistent. And indeed there is no compile time knowledge of what they will be. I can get around the problem of property consistency, it's just unfortunate that ExpandoObject is opaque to reflection (though I can see how that is a difficult problem to solve).
dkackman
In that case dynamic linq may help, but you may need a two-pass approach. Parse the data once to see which tags are encountered, and then another time to fill the list of new objects. I guess the problem is that if any mp3 file has a defined property, after you map the values to objects (dynamic or not) all of them have to have that property.
Egor
+1  A: 

my answer from http://stackoverflow.com/questions/1753187/dynamic-column-binding-in-xaml

I've used an approach that follows the pattern of this pseudocode

columns = New DynamicTypeColumnList()
columns.Add(New DynamicTypeColumn("Name", GetType(String)))
dynamicType = DynamicTypeHelper.GetDynamicType(columns)

DynamicTypeHelper.GetDynamicType() generates a type with simple properties. See this post for the details on how to generate such a type

Then to actually use the type, do something like this

Dim rows as List(Of DynamicItem)
Dim row As DynamicItem = CType(Activator.CreateInstance(dynamicType), DynamicItem)
row("Name") = "Foo"
rows.Add(row)
dataGrid.DataContext = rows
qntmfred
Interesting approach. I will likely have to do something similar but would like to avoid the Emit pieces. Using both Expando and Emitted types seems redundant. Thanks for the link; it's given me some ideas.
dkackman
+1  A: 

Ultimately I needed to do two things:

  1. Generate the columns manually from the list for 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 ExpandoObject's have the 
// same set of properties, get the complete list of distinct property names
// this represents the list of columns
IEnumerable<IDictionary<string, object>> rows = dataGrid1.ItemsSource.OfType<IDictionary<string, object>>();
IEnumerable<string> 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);
}
dkackman