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.

Setting the context. What is ModelItem for, anyway?

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.

  1. With this syntax, XAML parser sees “{Binding” and realizes it needs to process a Binding MarkupExtension.
  2. Binding class, which is MarkupExtension, has to process the markup extension – actually it goes through BindingBase, and initializes a Binding object with the PropertyPath we gave it, ModelItem.OwnerName
  3. The PropertyPath, when bound to an expression, creates callbacks based on INotifyPropertyChanged from the various objects in the path. Additionally, whenever it is evaluated, the path is traversed, and the traversal is compatible with each ‘property name’ in the path being one of three things (based on inference from PropertyPath constructor(string, object[]) ) (in unknown order of preference):
    -a) a real object property as in System.Reflection.PropertyInfo from the object’s Type
    -b) a dependency property, found by the property Name
    -c) a PropertyDescriptor from a TypeDescriptor of the object - or the object itself if it implements ICustomTypeDescriptor.

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.

 

Peeking at ModelItemImpl

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().

 

Using a Custom Type Descriptor with System.Activities.Presentation.ModelItem

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.

public class MyCustomTypeDescriptorActivity : CodeActivity, 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:

    public AttributeCollection GetAttributes()
    {
        return TypeDescriptor.GetProvider(this)
            .GetTypeDescriptor(this)
            .GetAttributes();
    }
 


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).)

    public PropertyDescriptorCollection GetProperties()
    {
        // Get default property descriptors
        PropertyDescriptorCollection r = TypeDescriptor.GetProvider(this)
            .GetTypeDescriptor(this)
            .GetProperties();
 
        // Copy them to an array
        int n = r.Count;
        PropertyDescriptor[] arr = new PropertyDescriptor[n + 1];
        r.CopyTo(arr, 0);
 
        // Add a new custom property descriptor to array
        SimplePropertyDescriptor pd = new SimplePropertyDescriptor();
        arrNo = pd;
 
        // Return a new PropertyDescriptorCollection
        var ret = new PropertyDescriptorCollection(arr);
        return ret;
    }
 

Lastly, we need to implement our own PropertyDescriptor type, SimplePropertyDescriptor.

    public class SimplePropertyDescriptor : PropertyDescriptor
    {
        public SimplePropertyDescriptor()
            : base("SimplePropertyDescriptor", new Attribute[0])
        {  
        }
 
        public override bool CanResetValue(object component)
        {
            return false;
        }
 
        public override Type ComponentType
        {
            get { return typeof(MyCustomTypeDescriptorActivity); }
        }
 
        public override bool IsBrowsable
        {
            get { return true; }
        }
 
        public override Type PropertyType
        {
            get { return typeof(string); }
        }
 
        public override object GetValue(object component)
        {
            return "Hello";
        }
 
        public override bool IsReadOnly
        {
            get { return false; }
        }
 
        public override void ResetValue(object component)
        {
        }
 
        public override void SetValue(object component, object value)
        {
            throw new NotImplementedException();
        }
 
        public override bool SupportsChangeEvents
        {
            get
            {
                return false;
            }
        }
 
        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }
    }
 

 

Does it work?

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. Smile

The rehosted property grid

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:

  1. Set IsBrowsable = true – actually I’m just going to say this is necessary in theory. It turns out that Workflow Designer doesn’t check this value right now, but I believe it probably should.
  2. Set IsReadOnly = false – read-only properties (e.g. with no set accessor) never show up in the Workflow Designer property grid.

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).


Blog Post by: tilovell09