Creating a Silverlight 5 Static Markup Extension

If you have done any WPF application development I am sure you have used and fallen in love with the Static markup extension.  If you’re are not familiar with it, the Static markup extension allows you to reference static fields and properties in your XAML markup.

For example; let’s assume we have a class with the following static field defined:

public class Common
{
    public static string StaticText = "This is text from a static property";
}

We can use this field in our WPF application as follows:

<Grid>
    <TextBlock Text="{x:Static ext:Common.StaticText}" />
</Grid>

 

NOTE: “ext” is a namespace that has been defined to instruct the XAML parser where to find our static field.

Pretty cool right?  Unfortunately if you are also doing any Silverlight development you will soon find that this wonderful and useful extension does NOT exist in Silverlight.  Luckily for us in Silverlight 5 we were given the ability to write our own custom markup extensions.  This can be done using either the IMarkupExtension or the abstract MarkupExtension class.

Now it’s time to create our own Static markup extension.  I want to point out that there is a naming convention when creating custom markup extensions.  The convention is as follows; ExtensionNameExtension.  The name of the extension is followed by Extension.  This is very similar to how you create attributes.  You won’t actually be using the suffix when define them in XAML. 

Let’s start by creating a new class called StaticExtension.  The StaticExtension class should derive from the MarkupExtension abstract class.  You will need to implement the abstract ProvideValue method.  The code I used for the Static markup extension is as follows.

/// <summary>
///  Class for Xaml markup extension for static field and property references.
/// </summary>
public class StaticExtension : MarkupExtension
{
    /// <summary>
    ///  The static field or property represented by a string.  This string is
    ///  of the format Prefix:ClassName.FieldOrPropertyName.  The Prefix is
    ///  optional, and refers to the XML prefix in a Xaml file.
    /// </summary>
    private string _member;
    public string Member
    {
        get { return _member; }
        set
        {
            if (value == null)
            {
                throw new ArgumentNullException("Member");
            }
            _member = value;
        }
    }

    /// <summary>
    ///  Return an object that should be set on the targetObject's targetProperty
    ///  for this markup extension.  For a StaticExtension this is a static field
    ///  or property value.
    /// </summary>
    /// <param name="serviceProvider">Object that can provide services for the markup extension.
    /// <returns>
    ///  The object to set on this property.
    /// </returns>
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (_member == null)
            throw new InvalidOperationException("member cannot be null");

        // Validate the _member
        int dotIndex = _member.IndexOf('.');
        if (dotIndex < 0)
            throw new ArgumentException("dotIndex");

        // Pull out the type substring (this will include any XML prefix, e.g. "av:Button")
        string typeString = _member.Substring(0, dotIndex);
        if (typeString == string.Empty)
            throw new ArgumentException("typeString");

        // Get the IXamlTypeResolver from the service provider
        IXamlTypeResolver xamlTypeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
        if (xamlTypeResolver == null)
            throw new ArgumentException("xamlTypeResolver");

        // Use the type resolver to get a Type instance
        Type type = xamlTypeResolver.Resolve(typeString);

        // Get the member name substring
        string fieldString = _member.Substring(dotIndex + 1, _member.Length - dotIndex - 1);
        if (fieldString == string.Empty)
            throw new ArgumentException("fieldString");

        // Use the built-in parser for enum types
        if (type.IsEnum)
        {
            return Enum.Parse(type, fieldString, true);
        }

        // For other types, reflect
        bool found = false;
        object value = null;

        object fieldOrProp = type.GetField(fieldString, BindingFlags.Public |
                                                        BindingFlags.FlattenHierarchy | BindingFlags.Static);
        if (fieldOrProp == null)
        {
            fieldOrProp = type.GetProperty(fieldString, BindingFlags.Public |
                                                        BindingFlags.FlattenHierarchy | BindingFlags.Static);
            if (fieldOrProp is PropertyInfo)
            {
                value = ((PropertyInfo)fieldOrProp).GetValue(null, null);
                found = true;
            }
        }
        else if (fieldOrProp is FieldInfo)
        {
            value = ((FieldInfo)fieldOrProp).GetValue(null);
            found = true;
        }

        if (found)
            return value;
        else
            throw new ArgumentException("not found");
    }
}

Now all I need to do is add a namespace to my Silverlight view and then use it in XAML as follows:

<Grid x:Name="LayoutRoot" Background="White">
    <TextBlock Text="{ext:Static Member=ext:Common.StaticText}" />
</Grid>

That’s it!  I will definitely be using this quite often.  I would like to mention that unlike in WPF where you don’t have to specify the “Member” property explicitly, in Silveright you have to explicitly set the Member property.  This is because there is not a ConstructorArgument attribute in Silverlight.  So until then you will need to have a little extra text in your markup syntax.


Add a Comment

Be the first one to add a comment. Please Login or Register to add comment.