Databinding to “Object Aggregates”

Filed in Software Developement Leave a comment

The Problem

We have a list of editable items (maybe displayed in a list view), and we have an item editor that can edit a single item.  We want to make our editor handle multiple items in a similar fashion that the visual studio property grid functions.  That is to say that when multiple items are selected, editable fields which are similar across all selected items are displayed, and fields that differ are blank.  The main problem here is that you can’t really databind to a collection of things, not in the sense that we want to solve this problem.  There is no straight forward way of doing this, so my solution is to create an object aggregator class that simulates a single object but in reality handles an enumeration of objects.

I can’t post any code because, while it does work, it is far from polished and i hate to publish unpolished code.  My description of how to do things should be more than enough for anyone to take a solution away from this post.

The Solution

Create a class called ObjectAggregator<T>.  Implement ICustomTypeDescriptor and INotifyPropertyChanged.  Add a public property called Objects which is an Enumerable<T>, this will store our object collection to aggregate.  The constructor should take the object collection and store it in the property.  Now implement the ICustomTypeDescriptor, almost all function and property implementations should look like “return TypeDescriptor.XXX(typeof(T));”  Basically, we are just passing the type description logic onto the actual type we are aggregating.  The exceptions are the GetProperties and GetPropertyOwner functions, this is where we customize things.  GetPropertyOwner just needs to return this, GetProperties is where the real fun occurs.

Get Properties

The empty parameter prototype can just call the other function with null as a parameter (this may be done in the aggregate type’s logic, but do it here just in case).  As for the standard GetProperties(Attribute[]), first get your original list of property descriptors by calling TypeDescriptor.GetProperties(typeof(T), attributes);. Now you can use Cast and Select to generate a list of ObjectAggregatorPropertyDescriptor<T> instances (more on this class below).  Now bundle your list up in a PropertyDescriptorCollection (by using ToArray) and return it.

ObjectAggregatorPropertyDescriptor<T>

This class will inherit from PropertyDescriptor and use the same generic type as the aggregator.  Your constructor will have to take a property descriptor (necessity of the base class), and you should store this paramater locally in a private property.  Now again, most of the property descriptor implementation is pass through.  The only important implementations are where we get or set data, GetValue, SetValue, and ResetValue.  This is because we must aggregate the object collecton held in the type descriptor.  So, most of your implementations should look something like BasePropertyDescriptor.XXX(component).  For the Set and Reset, just cast the component to the aggregator and iterate over the object collection performing the Set or Reset on each object (i.e. BasePropertyDescriptor.Reset(obj)).  As for get, i will post some code because describing LINQ is less fun:

return (component as ObjectAggregator).Objects.Select(x => BasePropertyDescriptor.GetValue(x)).Distinct().SingleOrDefault();

That may look a little crazy, but it’s actually quite simple.  Convert each object into the value returned by the property descriptor, squeeze the list down to unique values and give use the only value (when every object has the same value) or the default for the value type (when values differ between objects).

Thoughts

This is a crappy implementation, but it is dead simple, and it works for most simple use cases.  There are things i didn’t go over which could be considered improvements.  I didn’t talk about INotifyPropertyChanged, but this is pretty straight forward, push out any changes to any objects in the aggregator as if they were its own.  Ideally, all your property values would be Nullable so that value collisions are actually properly handled as a collision (rather than just using the default value), this is especially true for value types where a collision may hinder the experience (think bools).  I can’t remember, but i think calling GetPropertyOwner is ideal as well, it’s been a while since i really dived into custom type descriptors.  Finally, if you wanted to really make this robust, you would handle the scenario of multiple object types.  It’s not too much more code, but probably quite messy if you have name/type collisions, since you would really have to pick how to handle that yourself, there is no default way.  I think the property grid would likely filter out any name/type collisions (i.e. the property “Flag” is a bool on one object and an Enum on another object in the same collection).

Anyways, this is *A* way to solve this problem, not the most elegant, but definitely on the path to the most elegant.  This would definitely need some work to function in a more complicated scenario (with converters, editors, attributes, etc…).  But in the end, it does (poorly) what i intended it to do, that is allow me to databind a single field to an object collection.  I would love to hear of any other ways to accomplish this task.

, , , , ,

TOP