Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Creating a Custom Markup Extension in WPF (and soon, Silverlight)

Pete Brown - 09 March 2011

WPF currently, and Silverlight in v5, enables you to create your own custom markup extensions. Markup extensions are those little strings in XAML that are typically (but not always) enclosed in {curly braces} which, as the name implies, add new functionality to XAML. The most commonly used markup extensions are the {StaticResource}, {TemplateBinding}, and {Binding} extensions.

Creating your own markup extensions is a nice way to integrate your view of the world into the toolset. They're particularly popular with MVVM and similar toolkits.

This article is, in part, an excerpt from early work in Silverlight 5 in Action, the revised edition of Silverlight 4 in Action. SL5 in Action is due out by the end of 2011, and will have a MEAP (early access bits) around the time of the first public developer release of Silverlight 5.

Creating a Simple Markup Extension

There's not much ceremony to creating a markup extension. The amount of effort required is directly proportional to the complexity of what you're trying to do. For example:

public class HelloExtension : MarkupExtension
{
public HelloExtension() { }

public override object ProvideValue(
IServiceProvider serviceProvider)
{
return "Hello";
}
}

As shown in this example, to create a markup extension, you need only inherit from MarkupExtension and override the ProvideValue method. The empty constructor is necessary for use in XAML, specifically when you include additional parameterized constructors.

When you use the markup extension, you must declare the namespace as explained in section 2.1.2 (of Silverlight 4/5 in Action), and use the class name in your XAML. For example, the following XAML uses the HelloExtension markup extension we just created.

<Grid xmlns:ext="clr-namespace:CustomMarkupExtensions">
<TextBlock Text="{ext:Hello}" />
</Grid>

Because we followed the convention of naming our markup extension with the suffix "Extension", we refer to it simply as "Hello" not "HelloExtension". The convention is helpful, but not required. When run, the result

image

One interesting thing is that markup extensions can be blown out using property element syntax. So, you could use the curly braces as shown above, or you could get the same effect by writing it this way:

<Grid xmlns:ext="clr-namespace:CustomMarkupExtensions">
<TextBlock>
<TextBlock.Text>
<ext:Hello />
</TextBlock.Text>
</TextBlock>
</Grid>

This can be important for some scenarios where you need finer control over what you pass in, as we'll see later.

That was a super-simple example, but shows how little you need to do to create a markup extension. Next, we'll look at how to make an extension that takes in a parameter or two.

Creating a Markup Extension with Support for Parameters

Typically, markup extensions will be more complex and include the ability to accept parameters. As we previously saw, the built-in examples such as Binding, take in a number of discrete parameters which are used to provide key values as well as to alter their behavior.

This example shows how to create an interesting markup extension which will always return the maximum of two provided values.

public class MaxValueExtension : MarkupExtension
{
public MaxValueExtension() { }

public MaxValueExtension(object value1, object value2)
{
Value1 = value1;
Value2 = value2;
}

[ConstructorArgument("value1")]
public object Value1 { get; set; }

[ConstructorArgument("value2")]
public object Value2 { get; set; }

public override object ProvideValue(
IServiceProvider serviceProvider)
{
if (Value1 is IComparable && Value2 is IComparable)
{
IComparable val1 = Value1 as IComparable;
IComparable val2 = Value2 as IComparable;

if (val1.CompareTo(val2) >= 0)
return Value1.ToString();
else
return Value2.ToString();
}
else
{
return string.Empty;
}
}
}

Compared to the previous example, this adds a few new elements. First, we have a constructor accepting two parameters. Those two parameters are also provided as discrete properties. Note that the properties have been marked up so they map to the parameters - this is used by XAML serialization and, while not required, is a good practice.

Arguably, there's at least one cheat in this code: I coerce the final returned value to a string. That limits its usefulness for anything other than a Text or Content property. However, it keeps this example simple.

To use the markup extension, the syntax is similar. There's a subtle bug in this approach, however, which I'll explain after the example.

<Grid xmlns:ext="clr-namespace:CustomMarkupExtensions">
<TextBlock Text="{ext:MaxValue Value1=200,Value2=25}" />
</Grid>

In this example, you'd expect the TextBlock to display 200, but it displays 25. This is because the values, in the absence of any other type cues, are treated as strings. One way to force them to be treated as numbers is to break the statement out using the property element syntax described earlier. The next example shows the verbose approach to using the markup extension

<Grid xmlns:ext="clr-namespace:CustomMarkupExtensions"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<TextBlock>
<TextBlock.Text>
<ext:MaxValue>
<ext:MaxValueExtension.Value1>
<sys:Int32>200</sys:Int32>
</ext:MaxValueExtension.Value1>
<ext:MaxValueExtension.Value2>
<sys:Int32>25</sys:Int32>
</ext:MaxValueExtension.Value2>
</ext:MaxValue>
</TextBlock.Text>
</TextBlock>
</Grid>

This markup shows the expanded approach. While this is significantly longer than the previous example, it does provide complete control over the parameters. Note also that the MaxValue markup extension uses its full name MaxValueExtension when referencing its own parameters.

image

With the types correctly specified, it works as expected. Of course, you could build intelligence into your markup extension or use other ways of forcing the types. You could even create a markup extension that always returns an integer and use that as a parameter to your MaxValue extension.

Let's go the approach of adding some brains to our markup extension.

Creating a Markup Extension with Type Casting Support

So far, we've done nothing with the IServiceProvider parameter to the ProvideValue function. That object provides some interesting information about the target that this extension is going to populate. The IServiceProvider interface includes a GetService function which can return an IProvideValueTarget. Using that interface, you can access the target object and property, get property/type information from them, and set the property value directly.

In our case, we want to make sure that if we pass in two ints, but plan to use the result in a TextBlock, that the resulting int is converted to a string. If you leave the conversion out, you will get an error at runtime when you specify the types using, for example, this syntax (same approach as the earlier example):

<Grid xmlns:ext="clr-namespace:CustomMarkupExtensions"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<TextBlock>
<TextBlock.Text>
<ext:MaxValue>
<ext:MaxValueExtension.Value1>
<sys:Int32>100</sys:Int32>
</ext:MaxValueExtension.Value1>
<ext:MaxValueExtension.Value2>
<sys:Int32>90</sys:Int32>
</ext:MaxValueExtension.Value2>
</ext:MaxValue>
</TextBlock.Text>
</TextBlock>
</Grid>

As mentioned previously, you normally get strings as parameters unless you break it out and get explicit with what you're passing in. Once you do that, you have to build conversion smarts into your markup extension. Here's the full markup extension with the type conversion logic built in.

using System;
using System.Windows.Markup;
using System.Windows;
using System.Reflection;

namespace CustomMarkupExtensions
{
// totally optional. I have the return type here just so you know
// it's possible. With a return type of object, you wouldn't normally
// include the return type attribute
[MarkupExtensionReturnType(typeof(object))]
public class MaxValueExtension : MarkupExtension
{
public MaxValueExtension() { }

public MaxValueExtension(object value1, object value2)
{
Value1 = value1;
Value2 = value2;
}

[ConstructorArgument("value1")]
public object Value1 { get; set; }

[ConstructorArgument("value2")]
public object Value2 { get; set; }

private object GetMaxValue()
{
if (Value1 is IComparable && Value1 is IComparable)
{
if (((IComparable)Value1).CompareTo(Value2) >= 0)
return Value1;
else
return Value2;
}
else
{
// use val1
return Value1;
}
}

public override object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
return null;

// get the target of the extension from the IServiceProvider interface
IProvideValueTarget ipvt =
(IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));

DependencyObject targetObject = ipvt.TargetObject as DependencyObject;

object val = GetMaxValue();

if (ipvt.TargetProperty is DependencyProperty)
{
// target property is a DP
DependencyProperty dp = ipvt.TargetProperty as DependencyProperty;

if (val is IConvertible)
{
val = Convert.ChangeType(val, dp.PropertyType);
}
}
else
{
// target property is not a DP, it's PropertyInfo instead.
PropertyInfo info = ipvt.TargetProperty as PropertyInfo;

if (val is IConvertible)
{
val = Convert.ChangeType(val, info.PropertyType);
}
}

return val;
}
}
}

This example is more complex, but if you break down the code, there's nothing magical going on here. Inside ProvideValue, I check to see if we have a DependencyProperty or a regular property.  Based on the type of property we're accessing, I get the PropertyType and then, if the type is convertible, call Convert.ChangeType to handle the type conversion.

MarkupExtensions can provide some interesting functionality to your XAML. Of course, you'll want to temper this with testability concerns (it's harder to test this than logic in your viewmodel), but it can get you out of some scrapes, and otherwise clean up nasty glue/utility code you may need to write in your code-behind.

This was written against and tested on WPF4. I haven't tested this on Silverlight 5, so if you're reading this in the Silverlight 5 timeframe, your mileage may vary.

         

Source Code and Related Media

Download /media/74282/wpfmarkupextensionexample.zip
posted by Pete Brown on Wednesday, March 9, 2011
filed under:          

15 comments for “Creating a Custom Markup Extension in WPF (and soon, Silverlight)”

  1. Bigsbysays:
    On the money, as usual, Pete.

    This is my most awaited feature on SL5. My WPFs use this profusely.

    One thing I'm most fond of, and you show in your last example but don't emphasize enough (I say), is that you have access to the DependencyObject and DependencyProperty where the extension is being used. This gives you access to tons of things to play with: the object and its Type, property and its expected type and, even nicer, to the VisualTree.
  2. Charlessays:
    Hey, Pete. Like the blog, but the version of SyntaxHighlighter your using has some issues when viewed in Chrome. It's been that way for a few months now. It displays "<br siber__q92dpb7seovvtbh5__vptr="4c82490">" after every line making the code impossible to read. The demo on the SyntaxHighlighter home page seems to work fine, so I'm not sure exactly what the issue is.

    Also, the submit on your "Contact" page results in a 404.
  3. Tom McKearneysays:
    So, I guess a question I'd have here: Is this a better way to perform this task than to use a Converter? If not, are there some higher level examples of custom markup extensions you've written in the real world?
  4. Petesays:
    @Charles

    Thanks for the heads up. I'll look into it.

    @Tom

    MVVM toolkits tend to use these a lot for things like Method Binding. I personally haven't had to write any in any real-world apps because I did more Silverlight than WPF.

    @Denis

    Type converters convert from one type to another. They require that you have access to the source code for any class you intend to decorate to allow type conversion. Type converters are a .NET thing used throughout the framework and in WPF/SL.

    Value converters are used in binding in WPF/SL only. Before the StringFormat parameter, they were most often used to format data. They're great for taking user input and changing its format for storage, or taking data and changing its format for display.

    Behaviors (and attached properties) let you "attach" functionality or behavior to another element. Great for drag and drop etc. but, while used for such, they aren't optimized for the things you can do with markup extensions.

    There's overlap across them. Knowing which to use when is more a matter of experience than guidance.

    Pete
  5. Petesays:
    @Denis

    Forgot to mention: No you can't have DPs in the markup extension class - it doesn't derive from DO. I ran into that when I wanted to pass binding statements are value1 and value2. Since they aren't DPs, there isn't a nice clean way to do that.

    Pete
  6. Petesays:
    Hi Charles

    I'm posting this using the latest version of chrome. The code listings look fine, and if this posts, presumably no 404.

    Do you have an add-in or something that is interfering?

    Pete
  7. Yvessays:
    I've just tried this, and it basically worked fine. I have extended my markup extension class to take two values instead of one. But I couldn't use it inside a Binding expression anymore because Visual Studio cannot correctly read that code.

    Here's a description of the bug:
    http://www.hardcodet.net/2008/04/nested-markup-extension-bug

    And here's the Microsoft Connect bug entry:
    http://connect.microsoft.com/VisualStudio/feedback/details/509234/wpf-nested-markup-extension-bug

    So before you search for hours why your new extension doesn't work right, this might help you. The easiest solution is not to use the {markup extension} attribute syntax in these cases but to type out all XML elements in full. This is quite a few lines longer and less readable, but at least it works. Damn Microsoft, you should have fixed that 6 years ago!
  8. Luis Perezsays:
    Great article. I want to say that custom markup extensions are extremely powerful and that you can use them to even replace WPF's default binding. That's exactly what I did in order to simplify bindings, you can read about it and download it here: http://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html

    Now if I want to bind to a property instead of writing this:

    {Binding Path=Text, ElementName=MyTextBox}

    I can instead write this:

    {BindTo #MyTextBox.Text}

    Binding methods to functions is also easier, so is binding to templates, ancestors, etc.

    This is evidence of how flexible the custom markup support is.

Comment on this Post

Remember me