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 static string StaticText = "This is text from a static property";
}
We can use this field in our WPF application as follows:
????<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.
///??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:
????<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.
Interesting, however, is there a way to gain designer support? I’m using your extension to reference string resources (through the class generated by a .resx), but in the designer, either “the extension type” or an empty string is displayed.
By comparison, up to SL5, I was using the ResourceWrapper trick (similar to what’s explained here: http://sushilpatil.wordpress.com/2010/07/14/use-resource-file-in-silverlight-application/) and my (neutral lang) strings appeared in the designer after successful compilation of course.
For completeness, here is the code I use:
The reason that approach works in the designer is because you are creating a instance of the ResourceWrapper class in the App.xaml file making it global to your applications. Then binding to the instance property. My approach uses a true static markup extension that doesn’t required creating instances of classes.