Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Extend WPF: Add Your Own Keywords And Functionality To XAML With Custom Markup Extensions

DZone's Guide to

Extend WPF: Add Your Own Keywords And Functionality To XAML With Custom Markup Extensions

·
Free Resource

WPF is highly customizable and flexible. Also what makes WPF even better is XAML, the markup based declaration language, which is often associated to WPF but not limited to it. I am pretty sure whoever is reading this post have already wrote few XAML based UI and many of the readers have created a full blown WPF application. We use many XAML extensions in our day to day applications. All of these extensions derive from the System.Windows.Markup.MarkupExtension class. We use extensions like StaticResource, DynamicResource etc in everyday Xaml.

The curly braces “{ }” notation tells the XAML processor that it has encountered an extension and the Xaml processor responds accordingly by evaluating the text inside the curly braces. Here is an example on how extensions are used.

<Window.Resources>
<SolidColorBrush x:Key="mybackground" Color="#FF8080C8"/>
</Window.Resources>
<Rectangle Height="84" Name="rect1" Width="162" Fill="{DynamicResource mybackground}" />

In the above sample we have declared a rectangle element and we taking the fill color dynamically from the ‘mybackgorund’ solid color brush which is defined within the windows resources. By taking approach this we can also use the same brush in other WPF elements.

Markup Extensions

MarkupExtension is the base class for any Xaml extension. Just like .NET attributes which ends the name with the word ‘Attribute’, the markup extensions end the class name with the word ‘Extension’. They should be used in Xaml syntax without the appended word ‘Extension’ just like attributes.

[img_assist|nid=5183|title=|desc=|link=none|align=none|width=496|height=213]

The above diagram shows how extensions are inherited and how those classes are used with curly brace (“{ }” ) markups.

Custom Markup Extensions

XAML cannot do everything, and in any good application we need to write code. However if there are some code that are repetitive and we want to declare those code in XAML markup we can write our own markup extensions.  In order to do so we need to inherit from the abstract class System.Windows.Markup.MarkupExtension and implement the abstract method ‘ProvideValue’. See the signature of the provide value method below ...

public object ProvideValue(IServiceProvider serviceProvider)

Then we need to reference our namespace in Xaml and use the extension.

Relative Color Markup Extension : A Custom Extension example

There are many cares where we need to have a relatively lighter or darker color of a certain color. For example if we are designing a button and when the mouse moves over the button we want a lighter shade of the normal color of the button and when mouse is down we want a darker color. In order to do these things in XAML we would have to write triggers and 3 different colors. Lets assume we start with color blue and define blue, a lighter shade of blue and a darker shade of blue, then we would be in trouble if we decide to make our button green and have a light green, green and dark green color. We would have to change all 3 colors. Now wouldn’t it be nice if we could define one color and then have darker and lighter shade of it? If we decide to change color then we would have to change the base color only. See the example below:

[img_assist|nid=5184|title=|desc=|link=none|align=none|width=420|height=549]

Writing the custom markup extension

Lets name the custom markup extension that we are writing as RelativeColor, so the class name has to be ‘RelativeColorExtension’.

[MarkupExtensionReturnType(typeof(Brush))]
public class RelativeColorExtension : MarkupExtension

Here our class inherits from MarkupExtension and we are explicitly declaring that the return value is a brush. The MarkupExtensionReturnType attribute lets you specify the return type. However this is optional. You might not want to declare that, specially where the return type determined at runtime and can vary.

Before we proceed further lets see and example of just how this extension will be written in Xaml so that we can better explain how we will write the extension.

[img_assist|nid=5185|title=|desc=|link=none|align=none|width=510|height=155]

The Styles

In our custom markup extension we have 3 styles, Lighten, Normal and Darken. We will specify the base color and which style to apply in the Xaml markup. So we define an enum in the custom extension class like this.

public enum MorphStyle
{
Lighten, Normal, Darken
}

We need to define a property that will store this style

private MorphStyle _style = MorphStyle.Normal;
/// <summary>
/// Which style to render
/// </summary>
public MorphStyle Style
{
set { _style = value; }
}

The Reference Color

Since we need to refer to a color in the resource dictionary, we need to have a property for that as well.

private string _baseColorKey = null;
/// <summary>
/// This is the key of the reference color. This should be defined in a
/// dictionary
/// </summary>
public string BaseColorKey
{
set { _baseColorKey = value; }
}

In this variable the key (x:key attribute) of the reference color will be stored.

Overriding the ProvideValue

We simply have to override the ProvideValue method first like this.

/// <summary>
/// Overridden method returns the brush based on style and base color reference
/// </summary>
/// <param name="serviceProvider"></param>
/// <returns></returns>
public override object ProvideValue(IServiceProvider serviceProvider)

By the time this method is called the Xaml Processor have already set the two properties from our Xaml declaration ( we will see the Xaml declaration a little later in the post). So we have the resource key of the base color and how to render it. We are going to keep our resources in the Application resource file and load from there.

// Check if the key is valid
if (!string.IsNullOrEmpty(_baseColorKey))
{
object obj = Application.Current.FindResource (_baseColorKey);
if (obj != null)
{
coreColor = (Color)obj;
}

Deciding based on the style

Since we have found the referenced color, we can create a lighter or a darker version of it. (Disclaimer: The code we used to get darker or lighter version of a color in the static methods DarkenColor and BrightenColor are very simple and basic and may be a very simple and lame implementation. This was done in order to keep the irrelevant code simple so that we can easily transfer the idea of custom markup extension to the reader). Since the style property of the extension was filled by the Xaml Processor we can act on their values. See code:

// Lets return a solid color brush 
if (this._style == MorphStyle.Lighten )
{
return new SolidColorBrush(BrightenColor(coreColor));
}
else if (this._style == MorphStyle.Normal)
{
return new SolidColorBrush ( CopyColor (coreColor));
}
else
{
return new SolidColorBrush(DarkenColor(coreColor));
}

Using our extension in Xaml code

Xaml is not a WPF specific markup language, but it is most widely used in WPF and silverlight. In WFP Xaml files start element you will find code that look like this ...

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Then we use x:Key and other “x” based namespaces because the it is generally imported under ‘x’ namespace . We have build our custom extension in a dll and we need to import that as a name space to our code.

Custom Namespaces in Xaml

In order to use classes from another dll in Xaml we will need to introduce a custom namespace. The format for defining a custom namespace looks like this:

xmlns:namespace="clr-namespace:namespace;assembly=assemblyname"

In our case both the assembly name and the namespace is ‘MyXamlExtensions’ and the namespace that we will use is ‘ce’ which is a short for custom extension. So the entry for our custom extension will look like this in the root element of the Xaml document.

xmlns:ce="clr-namespace:MyXamlExtensions;assmebly=MyXamlExtension

We are going to import the namespace in the application resource file and define a style for a rectangle object:

<Color x:Key="MyBackColor" R="#80" G="#80" B="#C8" A="#FF" />
<Style x:Key="ActiveRectangle" TargetType="{x:Type Rectangle}" >
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Fill"
Value="{ce:RelativeColor BaseColorKey=MyBackColor,Style=Lighten}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="Fill"
Value="{ce:RelativeColor BaseColorKey=MyBackColor,Style=Normal}"/>
</Trigger>
</Style.Triggers>
</Style>

You can see in the above example that  we are referencing the ‘MyBackColor’ as a reference and using our markup extension without the word ‘Extension’ at the end and we are also setting the BaseColorKey and Style property. We have defined the style in a way so that a lighter shade of the color is assigned to the rectangle when mouse is over the rectangle at other time the normal shade is used. Also please notice that the properties are defined in the extension with comma separation. Now we will set the style to rectangle like this.

<Rectangle Height="93" Name="rectangle1" Stroke="Black" Width="243" Style="{DynamicResource ActiveRectangle}" >

If we want to change the mouse over and normal colors we could just change the base color defined at ‘MyBackColor’.

Now that we have created an extension we can use it anywhere. We can write our common tasks in code and make them into extensions and re-use them in different Xaml markups.

Sample source code

You can download the custom markup extension code from from here. Have fun and let me know what extension did you make.

Topics:

Published at DZone with permission of Shafqat Ahmed. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}