Markup extensions in Silverlight 5

(This post is a translation of a part of my original article about the new features of Sivlerlight 5 beta 1).

In XAML, the content of the attributes is by default considered as a constant to assign to the target property.

Thus the following code will put the value “hello” in the Text property of the TextBlock:

<TextBlock Text="hello" />


The content of an attribute might pass through an IValueConverter to convert it in the correct format. For instance, here, the string “red” will be converted to a SolidColorBrush:

<Rectangle Fill="Red"/>


However, the notion of “markup extension” has been added to make the content of attributes dynamic (much like the difference between a property and a data member). So for instance, we can define bindings with the “binding” markup extension:

<TextBlock Text="{Binding ElementName=lst, Path=SelectedItem.Text}"/>


In this case, the extension will pick an element of the context called “lst” and retrieve the “SelectedItem.Text” property to assign the value of field”Text”.


With markup extensions, it is also possible to use StaticResource for instance.


Therefore, Silverlight 5 introduce (inspired by WPF) capacity for the developer to make its own extensions by implementing the IMarkupExtension interface.


 


Implementation


To provide a sample, we will create the x:Static extension of WPF (which allows to get the value of a static property in a static class).


So we will develop a class that will implement IMarkupExtension by providing a [public object ProvideValue(IServiceProvider_serviceProvider)] method. In this method we need to use the IServiceProvider services to return the final value. In our case, a little bit of reflection will enable us to find what we want:

public class MyStaticExtension : IMarkupExtension<object>



{



    public string Source { get; set; }



    public string PropertyName { get; set; }






    public object ProvideValue(IServiceProvider serviceProvider)



    {



        Type type = Type.GetType(Source);






        PropertyInfo propertyInfo = type.GetProperty(PropertyName, BindingFlags.Public | BindingFlags.Static);






        return propertyInfo.GetValue(null, null);



    }



}


Here we do not use the IServiceProvider. However, it allows us to access different services such as host control, the common property or the current object.


For instance, on the following code:

<TextBlock Text="{HotSilverlight5:MyStaticExtension Source=HotSilverlight5.Tools, PropertyName=Property}"/>


It is possible in our extension to retrieve the following information:

IProvideValueTarget targetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;



IRootObjectProvider rootObject = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;




targetProvider.TargetObject: Instance of my TextBlock.


targetProvider.TargetProperty: Text.


rootObject.RootObject: Instance of my root UserControl.


This can enable us to make rich contextual extensions.


Note that in XAML, extensions can be referenced either in summary as above or in an exploded form as here:

<TextBlock Margin="10" HorizontalAlignment="Center" FontSize="20">



     <TextBlock.Text>



          <HotSilverlight5:MyStatic Source="HotSilverlight5.Tools" PropertyName="Property"/>



     </TextBlock.Text>



</TextBlock>


In addition, it is not mandatory to use the word “Extension”. Thus we can reference our extension as a “mystaticextension” or “mystatic”.


 


Extensions for events


Extensions can also appear at the level of the events in order to choose during runtime the handler that you wish to use:

<Grid Loaded="{HotSilverlight5:MyDynamicHandlerExtension}">


And our class will then look like this:

public class MyDynamicHandlerExtension : IMarkupExtension<Delegate>



{



    void MyHandler(object sender, RoutedEventArgs e)



    {



        // Mon handler



    }






    public Delegate ProvideValue(IServiceProvider serviceProvider)



    {



        IProvideValueTarget targetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;



        EventInfo targetEvent = targetProvider.TargetProperty as EventInfo;






        ParameterInfo[] pars = targetEvent.GetAddMethod().GetParameters();



        Type delegateType = pars[0].ParameterType;



        MethodInfo methodInfo = GetType().GetMethod("MyHandler", BindingFlags.NonPublic | BindingFlags.Instance);






        return Delegate.CreateDelegate(delegateType, this, methodInfo);



    }



}


Use cases



  • Creation of data on the fly (like for instance a UriMaker) as a kind of IValueConverter but more powerful




  • Provide automatic translations from a key




  • Search data (such as our MyStaticExtension) to retrieve non-directly accessible informations in XAML