Browse by Tags

All Tags » dynamic control   (RSS)

Recently I undertook the effort to rewrite my personal web page for a few reasons. I always like side projects for learning so I wanted to explore some "fun" ideas such as three dimensional objects and plasma (I blogged about those here) but also some more practical ideas like dynamic content and custom list boxes. In future posts with this series I'll be covering:

  • "The Matrix Effect" from my Blog tab
  • The custom panel I used for the list box in my Projects tab
  • Fun with physics in my Twitter tab

The full source for the personal website is available on the home page, so just click the link to download. You'll also need to install the Farseer Physics Engine and the Physics Helper.

I used Jounce as the main framework for this and integrated it with the Silverlight navigation framework.

The first thing I'll cover is some of the dynamic content. I wanted to be able to edit certain entries and update them easily without re-publishing the entire XAP file. The Biography page is an example of this: the content is loaded in a separate file so I can edit and uploaded without changing the XAP.

The External File

The external file is easy: I create an XML document that contains the right namespace declarations and the content I want to show. In this case, I'm using a RichTextBox but I could use any type of root level control I wanted. Obviously, an approach like this would require some extra measures in production applications due to the security risk (you lose control over what that external content may contain).

Here is a snippet of the file I dropped into ClientBin to make it easy to access by Silverlight:

<RichTextBox xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  IsReadOnly="True" Background="{x:Null}">
<Paragraph FontWeight="Bold">
  Summary<LineBreak/>
</Paragraph>
 <Paragraph>
  I am a Silverlight-focused developer/architect and technical project manager with sales and entrepreneurial experience, a passion for mentoring and public speaking, 
  and a strong social media presence.
<LineBreak/>
</Paragraph>
</RichTextBox>

The View Model

The view model is responsible for fetching and displaying the text. I take away the concern of the UI rendering and just deal with the XAML as a string. Because I want to be friendly inside of the designer, I also supply some values when in design mode (the Jounce base view model provides an InDesigner bool for this).

Here is the entire view model:

namespace JeremyLiknessSL.ViewModels
{
    [ExportAsViewModel(Constants.VIEWMODEL_BIO)]
    public class BioViewModel : BaseViewModel 
    {
        private const string SAMPLE_XAML = @"<RichTextBox xmlns=""http://schemas.microsoft.com/client/2007"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" "
                              +
                              @" IsReadOnly=""True"" Background=""{x:Null}""><Paragraph>This is a test text box.</Paragraph></RichTextBox>";
        private const string BIO_XAML = "Bio.xml";
                
        public BioViewModel()
        {
            if (InDesigner)
            {
                Text = SAMPLE_XAML;
            }
            else
            {                
                HelperExtensions.LoadResource(new Uri(BIO_XAML,UriKind.Relative), s => Text = s);
            }
        }

        private string _text;
        public string Text
        {
            get { return _text; }
            set
            {
                _text = value;
                RaisePropertyChanged(()=>Text);
            }
        }
    }
}

That looks simple, no? My "helper" method is designed to allow me to load any string resource from the web. In this case, I just pass a delegate that will receive the text when ready and do something with it. I silently absorb errors but of course would propagate those in a business application.

Here is the helper set of methods:

public static void LoadResource(Uri resource, Action<string> resourceLoaded)
{
    var wc = new WebClient();
    wc.DownloadStringCompleted += WcDownloadStringCompleted;
    wc.DownloadStringAsync(resource, resourceLoaded);
}

static void WcDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    var wc = sender as WebClient;
    var callback = e.UserState as Action<string>;
    if (wc == null || callback == null)
    {
        if (callback != null)
        {
            callback(string.Empty);
        }
        return;
    }
    wc.DownloadStringCompleted -= WcDownloadStringCompleted;
    callback(e.Result);
}

Notice I wire into the call and pass the callback as part of the state. Upon the return, I get my call back refrence back and either call back with nothing if there was an error, or with the actual result if it procesed correctly.

The Dynamic XAML

Next, I need to have a control I can bind the text to and have it render an actual XAML control. I decided to create a user control which contains a Grid surface to host the control:

<UserControl x:Class="JeremyLiknessSL.Controls.DynamicXaml"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">    
    <Grid x:Name="LayoutRoot"/>
</UserControl>

In the code-behind I'll expose a dependency property to bind to. Whenever the dependency property changes, the code-behind will attempt to load that control from the XAML and inject it into the grid. The code-behind looks like this:

public partial class DynamicXaml
{
    public DynamicXaml()
    {
        InitializeComponent();
    }

    public string Xaml 
    {
        get { return GetXaml(this); }
        set { SetXaml(this, value); }
    }

    public static DependencyProperty XamlProperty =
        DependencyProperty.Register("Xaml",
                                    typeof(string),
                                    typeof(DynamicXaml),
                                    new PropertyMetadata(null, _SetXaml));

    private static void _SetXaml(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((DynamicXaml)d).LayoutRoot.Children.Clear();
        var uiElement = XamlReader.Load(e.NewValue.ToString()) as UIElement;
        if (uiElement == null) return;
        uiElement.SetValue(NameProperty, Guid.NewGuid().ToString().Replace("-", string.Empty));
        ((DynamicXaml)d).LayoutRoot.Children.Add(uiElement);
    }

    public static string GetXaml(DependencyObject obj)
    {
        return obj.GetValue(XamlProperty).ToString();
    }

    public static void SetXaml(DependencyObject obj, string value)
    {
        obj.SetValue(XamlProperty, value);
    }
}

Now, with the control in place, I can databind to my view model in the main view. The binding in the biography view looks like this:

<UserControl...>
<Controls:DynamicXaml Grid.Row="1" Xaml="{Binding Text}"/>
</UserControl>

As you can see, very straightforward. My view model also supplies a nice "default" control in design time, so I can see the result in the Visual Studio and Blend designers without having to run the application. This means we have a design-time friendly, MVVM-friendly implemenation of dynamic runtime controls. It also means I can simply edit one file when my biography needs to be updated and push it out to reflect the latest information.

Jeremy Likness

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
};

switch(prop.ParameterType)
{
    case PropertyType.TextBox:
        TextBox box = new TextBox
                          {
                              TextAlignment = TextAlignment.Left,
                              TextWrapping = TextWrapping.Wrap,
                              HorizontalAlignment = HorizontalAlignment.Stretch,                                          
                          };
        box.SetBinding(TextBox.TextProperty, dbBinding);
        retVal = box;
        break;

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