Wednesday, July 7, 2010

WPF Markup Extensions

Dependency property support value resolution. For value resolution, it needs the support of Markup Extensions. May be the name of Markup Extensions is new to you but if you are familiar with WPF, you might have seen curly brackets "{}" while setting the properties. e.g.

Text = "{Binding ...}"

or

Background = "{StaticResource ...}"

Both of the examples above are the examples of application of markup extensions. Let us discuss what markup extensions are and how we can create custom markup extensions.

Namespace: System.Windows.Markup

Now let's for a moment assume a world of WPF development where there are no markup extensions (I mean no static / dynamic resource, same old way of .net binding in XAML). How difficult would it be to program in XAML? I think that is why Microsoft came up with this idea for providing an ease of development during XAML based development.

They can be used using XAML attribute and property element syntax.

Conventionally, the names of all extensions end with "Extension" but they are stripped of this when used in XAML. As you know BindingExtension is used as just "{Binding ...}".

Custom Markup Extensions:
There are situations when we need to write our own extensions. An extension is a sub class of MarkupExtension abstract class defined in System.Windows.Markup namespace. We need to implement ProvideValue method of this class in order to define a concrete extension. By going through the following lines you would realize that we can write extensions of many scenarios where the only solution, seems to be, is moving the code in code behind of the control.

Simple Example
WPF allows us to define custom extensions. A markup extension is extended from MarkupExtension abstract class defined in System.Windows.Markup namespace. For the custom extension, ProvideValue() method must be overriden. In order to define the return type of the markup extension, MarkupExtensionReturnTypeAttribute is used. Let us see the definition of our custom extension, named MyCustomExtension, below.

[MarkupExtensionReturnType(typeof(string))]
public class MyCustomExtension : MarkupExtension
{
public MyCustomExtension()
{
}

public MyCustomExtension(string txt)
{
this.SetText = txt;
}

public MyCustomExtension(string txt1, string txt2)
{
this.SetText = string.Format("{0} {1}", txt1, txt2);
}

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

string txt;
public string SetText
{
get
{
return txt;
}
set
{
txt = value;
}
}
}

You can see that we have specified string as the return type of this extension ([MarkupExtensionReturnType(typeof(string))]). The definiton of ProvideValue have also be provided. We have created a string property SetText .In addtion two constructors of this extension has been defined. This extension just include "Hello" with the text set in SetText property.

Now let us use this extension in XAML.

<Window x:Class="WPF_MarkupExtensions.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPF_MarkupExtensions"
Title="Window1" Height="300" Width="300">
<StackPanel>
<TextBox x:Name="txtBox" Text="{local:MyCustomExtension Shujaat Siddiqi}"></TextBox>
<TextBox x:Name="txtBox2" Text="{local:MyCustomExtension SetText = Muhammad}" />
<TextBox x:Name="txtBox3">
<TextBox.Text>
<local:MyCustomExtension SetText="Siddiqi" />
</TextBox.Text>
</TextBox>
<TextBox x:Name="txtBox4" Text="{local:MyCustomExtension Muhammad, Siddiqi}"></TextBox>
</StackPanel>
</Window>

The alias of namespace containing our extension is specified as local (xmlns:local="clr-namespace:WPF_MarkupExtensions") in Window tag. We have used the extension in three different ways.

1. {local:MyCustomExtension Shujaat Siddiqi} :
This would cause single parameter constructor to be used for instantiation of the extension.

2. {local:MyCustomExtension SetText = Muhammad}:
This would cause zero arguments constructor to be used. After instantitaion it sets the SetText property as 'Muhammad'.

3.  <TextBox.Text>
<local:MyCustomExtension SetText="Siddiqi" />
</TextBox.Text>

This has exactly same behavior as 2.

4. {local:MyCustomExtension Muhammad, Siddiqi}:
This would cause the two parameter constructor to be used for instantiation of extension.

Note: It must be remembered that XAML has no way for determining the data type as it is defined in XML. So in order to select which constructor to use for instantion, it just have a look at the number of arguments. It just uses the constructor with the same number of arguments irrespective of the datatype. This puts up a limitation for constructor during runtime. The runtime gets confused about the constructor to use and results in XamlParserException. So, in order to understand this, if we add the following constructor to the definition of MyCustomExtension then while instantiation in 1st scenario, it would result in an exception.

public MyCustomExtension(int txt)
{
this.SetText = string.Format("{0}", txt);
}

Visual Studio Designer issues:
Sometimes Visual studio designer gives weird response to the custom extensions. It might include the error list with messages like:
"No constructor of type MyCustomExtension has '1' parameter"


It might not load the view in the designer:

Or it might be underlying the markup extension usage in XAML designer:

These error messages should not bother you because as soon as you run the application they are compiled fine and the application runs fine. It seems that they are just designer defects in Visual studio.

No comments: