Today we’re going to explore some internal aspects of ModelItem and its subclass ModelItemImpl. For your reference, ModelItemImpl is an internal class, making what we talk about below ‘implementation detail’, according to a narrow view based on which APIs are actually visible by way of being marked ‘public’, and therefore more likely to change in future framework versions (than something actually marked public and therefore having a 1 line API doc somewhere). Now stop worrying about such minor details and read on, because this stuff is interesting.
When you getting started with writing custom activity designers, and especially if you haven’t done WPF before, shows up pervasively in your WPF Binding expressions.
<TextBox Content="{Binding ModelItem.OwnerName}”/>
What’s the point of all this?
ModelItem as conceived long ago gives you one really feature for doing WPF bindings, which is change notification. Instead of doing a WPF binding to a literal property on your activity, instead you can bind to a proxy property on a proxy object. Which are ModelProperty and ModelItem. This provides a benefit because ModelItem implements INotifyPropertyChanged. Your activity class itself typically does not implement this interface.
Fine in principle. But dig down a little deeper. What really happens when you bind to ModelItem.OwnerName? Particularly how does ModelItem.Owner name actually work as a property path to bind to, considering that ModelItem does not literally have a public property called OwnerName?
Let’s figure out how that works.
Here’s the basic things happening.
And possibly it would also support dynamic (C# 4.0) property access to an IDynamicMetadataObjectProvider.
Presumably the ModelItem will always work with the property path by one of these mechanisms. Also if ModelItem plays really nice we can expect it might allow us to use the same full set of mechanisms for representing the property on the object it wraps, and consistently surface them through to the PropertyPath targetting it just as if we were using a PropertyPath on the underlying object – just with the addition of change notification.
If you grab a ModelItem instance from your custom ActivityDesigner, I think you’ll consistently find that what you’ve actually got is a ModelItemImpl. And if you check out its interfaces, you can find that even though it is of an internal type, it has two public interfaces on it, ICustomTypeDescriptor and IDynamicMetadataObjectProvider, both of which, interestingly, appeared above.
Practically, which interfaces should it use to provide the properties to PropertyPath? For reasons of precedence, the best answer is to implement ICustomTypeDescriptor and in particular, ICustomTypeDescriptor.GetProperties().
If ModelItemImpl is going to play nice, and allow your WPF bindings to work consistently as they would have with the original objet, then it should try to respect the behavior of the object that it wraps, including where that object itself implements ICustomTypeDescriptor.
So let’s try it out by implementing ICustomTypeDescriptor.
What’s a reasonable default behavior for the ICustomTypeDescriptor methods? Well, bear in mind that I’m just trying this out for the first time, but I think the reasonable default implementation for ICustomTypeDescriptor methods could look like this:
In order for us to ‘add’ a property to our object via Custom Type Descriptor, we will need to deviate from the above template in GetProperties(). (And possibly, GetProperties(Attribute[] attributes).)
Lastly, we need to implement our own PropertyDescriptor type, SimplePropertyDescriptor.
Well, I’m happy to report that yes, ModelItem detects and interoperates with our property descriptor. We can see it in the property grid. And also use it with a ModelItem Binding expression.
Notes:
If you want your property to show up in the Workflow Designer’s property grid, there’s a couple things you need to do exactly right:
You can workaround the issue of IsBrowsable = false being ignored by adding a BrowsableAttribute(false) on your custom property descriptor (in the attribute array, in the call to the base constructor).