How do I dynamically generate columns in a WPF DataGrid?
Solution 1
Ultimately I needed to do two things:
- Generate the columns manually from the list of properties returned by the query
- 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);
}
Solution 2
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.
Solution 3
my answer from 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
Solution 4
Although there is an accepted answer by the OP, it uses AutoGenerateColumns="False"
which is not exactly what the original question asked for. Fortunately, it can be solved with auto-generated columns as well. The key to the solution is the DynamicObject
that can have both static and dynamic properties:
public class MyObject : DynamicObject, ICustomTypeDescriptor {
// The object can have "normal", usual properties if you need them:
public string Property1 { get; set; }
public int Property2 { get; set; }
public MyObject() {
}
public override IEnumerable<string> GetDynamicMemberNames() {
// in addition to the "normal" properties above,
// the object can have some dynamically generated properties
// whose list we return here:
return list_of_dynamic_property_names;
}
public override bool TryGetMember(GetMemberBinder binder, out object result) {
// for each dynamic property, we need to look up the actual value when asked:
if (<binder.Name is a correct name for your dynamic property>) {
result = <whatever data binder.Name means>
return true;
}
else {
result = null;
return false;
}
}
public override bool TrySetMember(SetMemberBinder binder, object value) {
// for each dynamic property, we need to store the actual value when asked:
if (<binder.Name is a correct name for your dynamic property>) {
<whatever storage binder.Name means> = value;
return true;
}
else
return false;
}
public PropertyDescriptorCollection GetProperties() {
// This is where we assemble *all* properties:
var collection = new List<PropertyDescriptor>();
// here, we list all "standard" properties first:
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(this, true))
collection.Add(property);
// and dynamic ones second:
foreach (string name in GetDynamicMemberNames())
collection.Add(new CustomPropertyDescriptor(name, typeof(property_type), typeof(MyObject)));
return new PropertyDescriptorCollection(collection.ToArray());
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes) => TypeDescriptor.GetProperties(this, attributes, true);
public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true);
public string GetClassName() => TypeDescriptor.GetClassName(this, true);
public string GetComponentName() => TypeDescriptor.GetComponentName(this, true);
public TypeConverter GetConverter() => TypeDescriptor.GetConverter(this, true);
public EventDescriptor GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(this, true);
public PropertyDescriptor GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(this, true);
public object GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(this, editorBaseType, true);
public EventDescriptorCollection GetEvents() => TypeDescriptor.GetEvents(this, true);
public EventDescriptorCollection GetEvents(Attribute[] attributes) => TypeDescriptor.GetEvents(this, attributes, true);
public object GetPropertyOwner(PropertyDescriptor pd) => this;
}
For the ICustomTypeDescriptor
implementation, you can mostly use the static functions of TypeDescriptor
in a trivial manner. GetProperties()
is the one that requires real implementation: reading the existing properties and adding your dynamic ones.
As PropertyDescriptor
is abstract, you have to inherit it:
public class CustomPropertyDescriptor : PropertyDescriptor {
private Type componentType;
public CustomPropertyDescriptor(string propertyName, Type componentType)
: base(propertyName, new Attribute[] { }) {
this.componentType = componentType;
}
public CustomPropertyDescriptor(string propertyName, Type componentType, Attribute[] attrs)
: base(propertyName, attrs) {
this.componentType = componentType;
}
public override bool IsReadOnly => false;
public override Type ComponentType => componentType;
public override Type PropertyType => typeof(property_type);
public override bool CanResetValue(object component) => true;
public override void ResetValue(object component) => SetValue(component, null);
public override bool ShouldSerializeValue(object component) => true;
public override object GetValue(object component) {
return ...;
}
public override void SetValue(object component, object value) {
...
}
dkackman
The first computer program I ever wrote was in BASIC on a TRS-80 Model I and it looked something like: 10 LPRINT "Don is cool" 20 GOTO 10 It only went downhill from there. Hey look I've got a WordPress site
Updated on October 02, 2020Comments
-
dkackman over 3 years
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 anExpandoObject
with dynamic properties representing the fields.It was my hope that
AutoGenerateColumns
(like below) would be able to generate columns from anExpandoObject
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.
-
dkackman over 14 yearsThe 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 over 14 yearsInteresting 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.
-
Egor over 14 yearsIn 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.
-
Wouter almost 13 yearsThis works nicely, but when do you execute this piece of code? The ItemsSource is not yet set when you handle this on DataContextChanged
-
dkackman almost 13 yearsIn my instance ItemSource is bound to a ViewModel property called Results. I have a INotifyPrpertyChanged handler in the view that reacts to that property changing.
-
Ninglin over 8 yearsThat's was my aproach but I stumbled upon a problem. What about Row Validation? Did you have to handle row validation on the ExpandoObjects?
-
dkackman over 8 years@Ninglin I did not need to do row validation for my use case
-
Sreerag G over 7 yearsThis doesn't seem to work for me when binding
ItemsSource
to anObservableCollection<MyObject>
-
Sreerag G over 7 yearsThis is the missing piece: reimers.dk/jacob-reimers-blog/…
-
Sphynx over 6 yearsThat link's now dead; could anyone post the full answer here?
-
Gábor over 6 yearsI've been using the scheme above in a program for years.
-
Sphynx over 6 yearsIt's the "..." pieces I was looking for; my assumption was you would hold some sort of keyed enumerable like a dictionary as a private field then generate the properties from the keys and get/set the values from the values in the dictionary. However, you have two hard-coded "..." string properties on
MyObject
- what are these for? -
Gábor over 6 yearsYou can ignore them if you don't need them (in the answer I mentioned "they can have both static and dynamic properties"). In my actual case, but YMMV, I had an object that had both fixed columns (like the name of the item) and dynamic columns (like prices of different quantity brackets, eg 1 piece, 2-10 pieces, 11-50 pieces, etc). So, you can have any number of "real" properties if you need them plus a set of dynamic properties generated on-the-fly. I'll add a few comments.
-
Smolakian about 3 yearsHere is a archive of that dead link: web.archive.org/web/20160828173518/http://www.reimers.dk/…