Writing a Custom WPF MarkupExtension

If you get into a situation where the built-in XAML functionality doesn’t quite meet your needs but you don’t want to move the functionality to code-behind every time you need it you may need to write your own custom MarkupExtension. Built-in markup extensions include BindingExtension, StaticResourceExtension, and RelativeSourceExtension. Anything that you use in XAML as a string that is surrounded by {} is a MarkupExtension. You may notice that the examples I mentioned all end in “Extension”, but when you use them in XAML the “Extension” part is missing (i.e. {Binding}). The naming is a WPF convention, similar to Dependency Property names ending in Property, but is also handled like .NET Attributes, which are declared as xxxAttribute but used as xxx. 

To start your new Markup Extension, you need to create a new class that derives from the MarkupExtension abstract base class. Try to choose a name that is descriptive but not too long (remember that most of it usage will be in XAML text with no Intellisense). Don’t forget to end the name with Extension. The only thing you are required to do after this is override the ProvideValue abstract method. The method signature from MarkupExtension is below. 

 public abstract object ProvideValue(IServiceProvider serviceProvider);

Now that you’ve added a ProvideValue override, you’re done! That is unless you actually want your markup extension to do something. You’ll notice that ProvideValue returns an object but most of the time you’re going to be targeting properties of a specific type with your extension’s output. To specify an output type there is a MarkupExtensionReturnType attribute that you can apply to the class (not the method) and supply a  Type argument. This return type will be checked by the XAML parser at compile time to make sure you’re assigning correct types to properties. 

 [MarkupExtensionReturnType(typeof(ImageSource))]

The next thing you’re probably going to need is some input arguments to act on. These take the form of normal get/set Properties. These can be any type you want. In most cases the XAML parser will be able to figure out the correct type from any string arguments passed in by the caller. When using the extension in XAML, the syntax is again borrowed from Attributes in that properties are passed in as a comma-delimited list of Name=Value pairs. You can also provide a one parameter constructor that maps a single non-named argument to one of the properties. You can see this in action with Binding by passing in just the value of the Path without the “Path=”. 

Now that the structure is set up and you have the arguments you need, you just need to write the logic to determine an output value. To assist you there are a few services that can give you more information about the calling XAML. The IServiceProvider parameter to ProvideValue allows access to these services. IXamlTypeResolver and IProvideValueTarget are two services which may be available from the IServiceProvider. There is very little documentation available on these services but they are listed on MSDN. Access these services using the following code. 

 IXamlTypeResolver resolver = (IXamlTypeResolver)serviceProvider.GetService(typeof(IXamlTypeResolver));   IProvideValueTarget target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));

After getting services, make sure to check for nulls. Guidelines instruct to not strictly depend on any specific service so no exceptions should be thrown if a service is not available. The type resolver parses types from type names. The provide value target gives you references to the TargetObject and TargetProperty of the MarkupExtension’s caller. These services can be useful but be careful how you use them as you can inadvertently break the entire object you’re applying the extension to. 

Using a markup extension is just like using any custom object in XAML. Add an xml namespace declaration, like xmlns:local=”clr-namespace:MyCustomNamespace” for the current assembly or xmlns:controls=”clr-namespace:CustomControlNamespace;assembly=CustomControlLibrary” for a referenced assembly. Then use your custom MarkupExtension along with the xmlns prefix and the Extension omitted from the name.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>