Using an ExpressionTextBox in a custom property editor

I have fielded a couple of questions from customers and also a forum post asking how to get the binding correct for the ExpressionTextBox correct in the property grid (well Eric fielded that one with some help on our side, thanks Eric!). For the record here are the details and an explanation.

WPF provides an extensibility model that allows you to create custom value editors for properties, here is a walkthrough that demonstrates the process. In your custom editor, you can use any old WPF control that you want, so you can go ahead and use an ExpressionTextBox if you feel like it. The problem is getting the binding for the expression correct. Usually, you bind the ExpressionTextBox to the ModelItem, this is ExpressionTextBox 101. However, in the property grid, you have the property value and not the ModelItem available for binding. What to do?

ModelPropertyEntryToModelItemConverter to the rescue (try to say that five times fast). This is a converter provided by the WF designer that gets the ModelItem associated with a given property. Here’s how you use it. Imagine you have a custom property editor in a Grid. You want an ExpressionTextBox for binding to the string argument MyArgument. I will pretend that you have gone ahead and written the guts of the property grid code and most likely slapped them in EditorResources.xaml, and all you need to do is add the ExpressionTextBox.

So let’s begin with the required incantations in your resource dictionary:

   <ResourceDictionary>
      <sapc:ArgumentToExpressionConverter x:Key="ArgumentToExpressionConverter" />
      <sapc:ModelPropertyEntryToModelItemConverter x:Key="ModelPropertyEntryToModelItemConverter" />
  </ResourceDictionary>

Now in your Grid, you have to add a DataContext that binds the property in the property editor to the model item.

     <Grid DataContext="{Binding Converter={StaticResource ModelPropertyEntryToModelItemConverter}}">
        <!-- write something awesome here -->
        <sapv:ExpressionTextBox 
            Expression="{Binding Path=ModelItem.MyArgument, Mode=TwoWay, Converter={StaticResource ArgumentToExpressionConverter},  ConverterParameter=In }"
            ExpressionType="sys:String"
            OwnerActivity="{Binding Path=ModelItem}" 
         />
    </Grid>

You can see that now we are back to basics on the ExpressionTextBox and using our friend the ArgumentToExpressionConverter to bind the expression to the argument on the ModelItem. This is all possible because of the binding on the DataContext.

So that’s all well and good if you know the name of the argument. What happens now if you want to write a generic property editor for any old argument? ArgumentToExpressionModelItemConverter to the rescue. This particular converter inherits from IMultiValueConverter, not IValueConverter, so it can be used in a MultiBinding. This is important, because not only do you need to update the value of the expression, you also need to set the parent activity of the expression.

Now the incantation for your resource dictionary looks like this:

   <ResourceDictionary>
      <sapc:ArgumentToExpressionModelItemConverter x:Key="ArgumentToExpressionModelItemConverter" />
      <sapc:ModelPropertyEntryToModelItemConverter x:Key="ModelPropertyEntryToModelItemConverter" />
  </ResourceDictionary>

And now your Grid looks like this:

     <Grid DataContext="{Binding Converter={StaticResource ModelPropertyEntryToModelItemConverter}}">
        <!-- write something awesome here -->
       <sapv:ExpressionTextBox 
                ExpressionType="s:Double"                
                OwnerActivity="{Binding Path=ParentProperty, Converter={StaticResource ModelPropertyEntryToOwnerActivityConverter}}"                
            >
                <sapv:ExpressionTextBox.Expression>
                    <MultiBinding Mode="TwoWay" Converter="{StaticResource ArgumentToExpressionModelItemConverter}" ConverterParameter="In" >
                        <Binding Path="Value" Mode="TwoWay"/>
                        <Binding Path="ParentProperty" Mode="OneWay" />
                    </MultiBinding>
                </sapv:ExpressionTextBox.Expression>
            </sapv:ExpressionTextBox>
    </Grid>

Of course, this being WPF, there is more than one way to skin this cat. Say you don’t like putting a DataContext on your Grid, and you’d rather just bind the properties directly. ModelPropertyEntryToOwnerActivityConverter to the rescue.

Chant with me:

   <ResourceDictionary>
      <sapc:ArgumentToExpressionModelItemConverter x:Key="ArgumentToExpressionModelItemConverter" />
      <sapc:ModelPropertyEntryToOwnerActivityConverter x:Key="ModelPropertyEntryToOwnerActivityConverter" />
  </ResourceDictionary>

Now go forth and bind your properties:

 <Grid>
    <!-- write something awesome here -->
    <sapv:ExpressionTextBox 
            ExpressionType="s:Double"
            OwnerActivity="{Binding Path=ParentProperty, Converter={StaticResource ResourceKey=ModelPropertyEntryToOwnerActivityConverter}, Mode=OneWay}" 
    >

    <sapv:ExpressionTextBox.Expression>
        <MultiBinding Mode="TwoWay" Converter="{StaticResource ResourceKey= ArgumentToExpressionModelItemConverter}" ConverterParameter="In" >
            <Binding Path="Value" Mode="TwoWay"/>
            <Binding Path="ParentProperty" Mode="OneWay" />
        </MultiBinding>
        </sapv:ExpressionTextBox.Expression>
    </sapv:ExpressionTextBox>
</Grid>

Finally, you’ll notice one annoying thing in the last two examples. The ExpressionType is fixed as a double. This is problematic for a general purpose property editor. Now we don’t provide a converter for converting types to argument types. I’ll talk about this process of conversion in a separate post.

[edit 3/29 – see Dynamically binding the expression type in the property grid to show how the conversion works. Also, there is some sample code with a property editor attached to that post.]

PS - When this post gets converted to MSDN documentation, I will make sure that the managed reference documentation is updated as well to show the actual use of these converters. :)