Silverlight 5 Tidbits–Markup Extensions
Published on Friday, April 22, 2011 12:30:00 PM UTC in Programming
Edit 2011-12-10: This article is compatible with the final version of Silverlight 5 (5.0.61118.0).
This post is part of a mini series about Silverlight 5:
- Implicit Data Templates
- Multi-Column Layouts
- Markup Extensions
- Multiple Windows
- Trusted Applications
- Incremental Search
- Data Binding
Even though all Silverlight developers are using markup extensions all the time, a lot of them are not aware of what they actually are. The XAML parser is able to convert attribute values (strings) into objects. With markup extensions, the parser can be advised to reference an already existing object somewhere else for this. For example, the StaticResource Markup Extension uses a key to evaluate a reference to a resource defined in a resource dictionary. The Binding Markup Extension creates an expression to defer the evaluation until run time, so the actual property value can be retrieved by using the data context. In this post we'll take a look at how you can create your own extensions in Silverlight 5 and what the limitations are.
The situation so far
Custom Markup Extensions have been a well-known feature in WPF, but are unavailable in Silverlight up to version 4. There's always a way around this limitation; for example, you can always create your data context in a way that already provides property values in the required format, you can make use of value converters or additional constructs like attached properties and the like.
However, often this requires writing more code, sometimes repetitive, or to create additional properties that you'd otherwise not expose. A markup extension can help with this by simplifying the XAML and surrounding code you have to write.
What's new in Silverlight 5
Silverlight 5 now offers the required interfaces and/or base classes to create your own markup extensions and the XAML parser knows how to handle them. Let's look at a simple example that explains what have been said so far a bit more in detail. In particular, we'll recreate the x:Static Markup Extension that is missing in Silverlight.
Creating a first markup extension
Until now, if you wanted to use a static property in Silverlight XAML, you needed to create some sort of wrapper that has a non-static property and create an instance of this wrapper as a resource somewhere, so you could use it with the "StaticResource" markup extension. Sounds clumsy? It is! In Silverlight 5 you can write a custom markup extension that mimics the behavior of WPF's original "x:Static" markup extension. To this end, you have to create a new class that either derives from the abstract "MarkupExtension" class or that implements the generic "IMarkupExtension" interface. (Note: the abstract "MarkupExtension" class uses the same interface, typed to object.)
public class StaticExtension : IMarkupExtension<string>
The convention here is to add an "Extension" suffix. Just as with attributes, you don't actually have to use this suffix when you use the extension in XAML later on (just "Static" will work in this case, see below). In the example, the extension is typed to string, which means that the only method defined in the interface ("ProvideValue") must return a string. For now, we'll just return a fixed value in this method:
// Implementation of IMarkupExtension<string>
public string ProvideValue(IServiceProvider serviceProvider)
{
return "Test";
}
This already is a working markup extension. You can use it in XAML like so (note the missing "Extension" suffix):
<TextBlock Text="{local:Static}" />
The "local" namespace points to the namespace where the "StaticExtension" class is defined in, just like with any other Xml namespace. Of course this is not tremendously useful yet, because if you run the application you'll see that the text block simply shows "Test" (just as expected).
Adding arguments
For the extension to be useful, we must create a possibility for the developer to pass arguments to it. This is done using properties. If we want to access a static property in our markup extension, the developer must provide some information about where this property is located. So let's add a "Path" property:
public string Path
{
get;
set;
}
This property is of type string because we want the developer to provide a class and field name here. You can also make these properties one of the primitive types (integer etc.), and the XAML processor will try to convert it to this type for you.
I've changed the new "ProvideValue" method to simply bounce the set "Path" value:
// Implementation of IMarkupExtension<string>
public string ProvideValue(IServiceProvider serviceProvider)
{
return Path;
}
In XAML, we can now pass in values to the extension using a syntax that should be familiar to you from other extensions:
<TextBlock Text="{local:Static Path=MarkupExtensionTest.Demo.MyStaticProperty}" />
If you run the application now, you will see that it actually displays:
This means that the improved extension works, we can now pass in arguments. You can extend that to multiple arguments by adding more properties to the extension class, and separating their values with commas in XAML when you use the extension.
Adding functionality
First of all let's create the "Demo" class that has the static property we want to use:
public static class Demo
{
public static string MyStaticProperty = "Value from a static property";
}
Now we only need to add the required logic to the markup extension's "ProvideValue" method to retrieve this value. In particular, we do:
- Make sure the user has set the "Path" and that it's valid.
- Extract the full qualified type name from this property's value.
- Retrieve the actual type by the full qualified name.
- Get the field info from the type, again using the information from the "Path" value.
- Retrieve the value of the static field from the field info and call ToString() on it.
Here is the code for that:
// Implementation of IMarkupExtension<string>
public string ProvideValue(IServiceProvider serviceProvider)
{
if (string.IsNullOrEmpty(Path))
{
return string.Empty;
}
// make sure the provided property path is sufficient
string[] parts = Path.Split(new char[] { '.' },
StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 2)
{
throw new ArgumentException("Invalid path.",
"Path");
}
// extract the type part and get the type
string typeString = String.Join(".", parts, 0, parts.Length - 1);
Type type = Type.GetType(typeString, true, true);
// get ahold of the field
FieldInfo field = type.GetField(parts[parts.Length - 1],
BindingFlags.Public | BindingFlags.Static);
if (field == null)
{
throw new ArgumentException("No such public static field found.",
"Path");
}
// retrieve the static value
object value = field.GetValue(null);
if (value == null)
{
return string.Empty;
}
// return the converted value
return value.ToString();
}
Of course this is a simplified implementation that doesn't work in all situations, and it also requires the full qualified name for the type (like in the sample XAML snippet above), but it's a good start. If we now run the application, we can actually see:
That's it! Your first markup extension in Silverlight 5 is done, and it successfully mimics WPF's x:Static feature in a simplified way.
Element Syntax
Instead of specifying your markup extension in attributes using the well-known curly braces, you can also make use of the element syntax. The following XAML is equivalent to what we've expressed above using attributes:
<TextBlock>
<TextBlock.Text>
<local:Static Path="MarkupExtensionTest.Demo.MyStaticProperty" />
</TextBlock.Text>
</TextBlock>
Limitations
One somewhat annoying limitation is that so-called positional arguments are not supported in Silverlight 5 (see the remarks section here). In WPF, it's possible to use constructors in your extension like:
// This would work in WPF
public Static(string propertyPath)
{
PropertyPath = propertyPath;
}
You can then simplify the XAML markup to:
<TextBlock Text="{local:Static MarkupExtensionTest.Demo.MyStaticProperty}" />
This is shorter and more elegant, and if there's only one argument like in this case it won't even hurt clarity at all. Unfortunately, if you try this in the Silverlight 5 beta, you receive an error:
Parser internal error: Markup extension scanner 'UnknownPositionalParameter'.
It's not clear to me why this limitation is in place, because the built-in markup extensions seem to use this feature already. However, even with that limitation the possibility to create custom markup extensions is a valuable addition to Silverlight.
Tags: Silverlight · Silverlight 5