Monday, October 5, 2009

Dynamic Silverlight Controls

I've had some fun with dynamic controls lately. Wanted to share a few caveats that I found for those of you trying to spin some of your own.

The premise is simple: we have XML configuration that drives the UI, so based on the values parsed from the XML, we generate the appropriate control.

I implemented this by created a data container for the XML. I parsed the XML using LINQ to XML and then databound my UI elements to the object graph. The main work is done with a converter for the control. The XAML looks like this:

<ContentControl DataContext="{Binding}" Content="{Binding Converter={StaticResource ParmControl}}" HorizontalContentAlignment="Stretch" Margin="2"/>

As you can see, I'm "bound" to my property, with the content of the convert being passed to a converter. The convert itself returns the dynamic Silverlight control. The code returns a FrameworkElement, but builds out the appropriate type of control based on the type of the property passed in. For example, for the text box, we'll create a binding and then return a TextBox like this:

Binding dbBinding = new Binding("Value") 
    Mode = BindingMode.TwoWay,
    NotifyOnValidationError = true,
    ValidatesOnExceptions = true

    case PropertyType.TextBox:
        TextBox box = new TextBox
                              TextAlignment = TextAlignment.Left,
                              TextWrapping = TextWrapping.Wrap,
                              HorizontalAlignment = HorizontalAlignment.Stretch,                                          
        box.SetBinding(TextBox.TextProperty, dbBinding);
        retVal = box;

Note that I am still databind the control to my own property, so as the user interacts with the UI, we will still get appropriate updates. The property itself exposes a method that returns an XElement fragment, so that when we save, we simply walk the object graph and append the fragments until we have the original XML document we began with to post back to the server (of course, there are some optimizations, like only sending values that changed - this is done with an IsDirty flag on the property).

The only caveat so far is that the content container needed to set HorizontalContentAlignment, something that threw me off for a bit until I realized what the property does. Note the convention for setting the binding: we set the binding on the control, but then pass the "owner" of the binding, which in this case is the TextBox class itself. That's the way those dependency properties work.

What was more interesting was generating a combo box. The combo box shows a nice enumeration of values (key/value) but the value on my property is just a simple value. Therefore, having the combo box bind properly meant having a value converter. When attaching value converters dynamically, a few things to keep in mind ...

When you specify the converter in your XAML with a key, what really happens is a single instance of the converter is created and referenced by the key. Whenever you reference the converter, you are referencing the same instance and the framework handles pulling your properties/bindings and passing them into the converter.

To follow this pattern, instead of creating a new instance every time I spin off a combo box, I have a single instance in my control converter:

private static readonly ParameterEnumConverter _parameterEnumConverter = new ParameterEnumConverter();

Then the converter is simply set to the binding:

dbBinding.Converter = _parameterEnumConverter;

In this case, I'm going to bind two items to the ComboBox - first, the binding to update my actual property (the dbBinding you saw earlier), and second, the binding for the source collection of items. I happen to hold those items in a property called Enumerations, so the second binding looks like this:

Binding comboBinding = new Binding("Enumerations") { Mode = BindingMode.OneWay };
// set the actual item binding
comboBox.SetBinding(Selector.SelectedItemProperty, dbBinding); 
// set the drop down list binding 
comboBox.SetBinding(ItemsControl.ItemsSourceProperty, comboBinding); 

In both cases I'm traversing up the tree to where the properties are derived, so instead of passing the ComboBox property, its the Selector (which the combo uses for the selected item behavior) and the ItemsControl (which the combo inherits from).

Likewise, the check box would bind to ToggleButton.

Once all of the magic is done, the items emit into the control container and become happy, active members of the Silverlight application. Thanks to databinding, all I have to do is plug into a save event and grab my root object to parse out the changes.

Jeremy Likness