Friday, September 21, 2012

DynamicObject & WPF Binding

In this post we are going to find out how .net 4.0 dynamic types can help us introducing dynamically generated properties which could be used by WPF binding system.

Properties with dynamic types in a Custom Class:
We can always declare properties with dynamic type. It allows to bypass static type checking along with duck-type support for our members. This is unlike object type where if it is not provided by the object type then we needed to use reflection in order to fulfill static type checks. Please remember that the properties of an object marked as dynamic is already supported in .net framework 4.0. Those properties have a dynamic type but they are not dynamically generated. Their usages are just replaced by expressions at compile-time. At runtime, these expressions are going to resolve and could cause binding exceptions if the assigned type does not support the expected members. Further detalis can be found here. Anyhow, WPF binding system does support binding to properties with dynamic type.

.net framework dynamic feature has three main types including DynamicObject, ExpandoObject and IDynamicMetaObjectProvider. As a matter of fact, DynamicObject can be smartly used to support dynamically generated properties. In the following example, let's look at a type called ViewModelBase. We can use this type as a base for all those view models which might need dynamically generated properties. This example is using an overlapped concept of model and view model. Most of the time, you might want your view models to be having dynamically generated properties based on the data streams available through XML or JSON. You might read some data from your database and might want to add properties based on this data.

Overriding DynamicObject's members including TrySetMember and TryGetMembers is providing us the means to introduce dynamically generated information which could be used as properties for WPF Binding. TrySetMembers is used by the runtime when WPF binding system updates the value of the property. On the other hand, TryGetMember is used to get the value of the property. You can see we have even implemented INotifyPropertyChanged to provide change notifications for these dynamically generated properties.

The dynamic members and their values are maintained in a Dictionary. We can simply make the members as of dynamic type. But since there is no expected behavior, I have just kept it as System.Object. The collection is filled up the first time this Dictionary is used in the code. We are simply filling it up the collection in the constructor using passed in values in the parameter propertyNames.

Unlike an ExpandoObject, the access to members of DynamicObject are statically checked in c# code at compile time. That is why we need to provide a special pair of methods to get and set the properties identified by its name. The members are named as SetPropertyValue and GetPropertyValue.

We need to pay special attention to the constructor. This is accepting the list of perceived dynamically generated members. Child types can also pass the members when their constructor is called for instantiation. This is exactly what we have done in the case of MainViewModel. It just needs a property, called MainTitle. This is passed to the base class which would do the whole magic required for the binding system to work.

We are setting the value of MainTitle in the constructor. Let's try to use this view model as a DataContext. The following is a view called MainWindow. Here we are binding MainTitle to a TextBox's Text property. With a TextBox, we can test if it is set and get as expected.

Let's run this now. The view is loaded on the screen as follows. put some break points in your code and try to update the Title. You should notice the breakpoints being hit. You should eventually see ViewModelBase.TryGetMember being called.

Basically we haven't even created a type with dynamically generated properties. We have created an object which could hold name / value pairs which supports DataBinding. There might be types inheriting from ViewModelBase which takes the property names as input parameters of their constructors. Now the same type can be instantiated with different property names and hence it would have different perceived properties. Now we can have different products supporting different properties altogether. This is different than the concept of a type which has specific set of properties.

One issue with using DynamicObject for such purpose is the absence of type description. We have been using the termss "perceived" and "fake" throughout this post as they are not real. There is no way we can find out what members are supported except keep try setting their value. A type should provide a mechanism to expose its type information. We should be able to query for its members. In the DynamicObject based implementation, there is no way we can find out what properties are there for an object's type.

Collections of DynamicObject based Types
Now let's see how we can handle collection of DynamicObject type of objects. Here we are adding a collection called Students. This is of type StudentViewModel which is eventually based on DynamicObject. We have also added an AmazingStudent property which is to hold an amazing student from this collection.

The following XAML code uses the collection for Binding. We are using the collection to bind to a ListBox and a DataGrid. As you can see we can bind element's members from a collection using a DataTemplate in the ListBox. The columns in the DataGrid can be directly bound.

Let's run this again. Now we can see the collection members correctly shown in the two panels. The view is loaded as follows:

As we know DataGrid has this amazing capability of showing auto-generatd columns using its AutoGenerateColumns property. Setting the proerty to true allows us to bind a collection holding auto-generated properties. We can use it to bind types including DataTable with no information about its column names. DataGrid discovers these columns and binds to them. Please remember that this auto-generation does not support DynamicObject based types. Let's update the DataGrid code as follows:

Now we try to run it again. Please notice the empty grid in the view.

Download Code


Unknown said...

Hi! Did you manage to get the DataGrid autogenerate the columns??

Muhammad Shujaat Siddiqi said...

Hi José,

AutoGenerateColumns does work for DataTable but couldn't really get it to work for custom types, hence this post. But if you just follow through the example then you can find this as a workable solution. You can also have a look at this post which discusses another solution: