h3mm3's blog

Stories from behind the keyboard

  • RSS
  • Twitter

In this post I’m showing how to create a simple XAML markup extension. A markup extension is a class derived from MarkupExtension, and it can be used to provide further extensibility to the XAML markup code. The custom markup extension showed here (called AutoTagExtension) will provide auto-numbering capability to any element (class) or attribute (property) of the markup code. Moreover, this markup extension is likely to be useless in a LOB scenario.

Any instance of the class AutoTagExtension can be configured with a Prefix (a custom string) and will produce a formatted string like “<Prefix><Count>,” where <Prefix> is the prefix set in the XAML by the designer, and <Count> is an Integer automatically generated based on the number of elements (having the same Prefix) already present in the markup. Since this markup extension will return a string, I’ll use this extension in standard properties such as Run.Text and Button.Content. For instance:

<Button Content="{AutoTag Prefix=AButton}"/>


The markup extension



The class AutoTagExtension is declared as follows (note that the suffix –Extension can be stripped out when using the class in the XAML code)



using System;
using System.Collections.Generic;
using System.Windows.Markup;

namespace WpfMarkupExtentionsDemo
{
public class AutoTagExtension:MarkupExtension
{
//Default constructor: enables writing {AutoTag} in the markup
public AutoTagExtension()
{

}

//Parameterized constructor: enables writing {AutoTag myPrefix} in the markup
public AutoTagExtension(string prefix)
{
Prefix = prefix;
}

//Use {AutoTag myPrefix,true} to reset the count of “myPrefix” elements
public AutoTagExtension(string prefix, bool resetCount)
{
Prefix = prefix;
if (resetCount) ResetCount(prefix);
}

//Public instance property: enables writing {AutoTag Prefix=myPfx} in the markup
public string Prefix { get; set; }

//This one makes the magic happen
public override object ProvideValue(IServiceProvider s)
{
return String.Format("{0}{1}", Prefix, GetCount(Prefix));
}

#region Static members provide counting capability
private static readonly Dictionary<string,int> _counts 
= new Dictionary<string, int>();

//C# doesn’t support static indexers, so I write a “classic” getter function...
protected static int GetCount(string prefix)
{
if (_counts.ContainsKey(prefix))
{
return ++_counts[prefix];
} else
{
_counts.Add(prefix, 0);
return 0;
}
}
//This one enables resetting the counter
protected void ResetCount(string prefix)
{
_counts.Remove(prefix);
}

#endregion
}
}


Using my custom markup extension in the XAML code



The usage of the extension in the XAML code is straightforward: simply declare a namespace alias for the library containing the markup extension (e.g. “my”) and use it as follows:.



<StackPanel>
<Button Content="{my:AutoTag Button,true}"/>
<Button Content="{my:AutoTag Prefix=Button }"/>
<TextBlock>
<Run FontStyle="Italic" Text="{my:AutoTag 'Run '}"/>,
<Run Text="{my:AutoTag 'Run '}"/>
</TextBlock>
<Button Content="{my:AutoTag Button, true}"/>
</StackPanel>


The first usage invokes the constructor that accepts two input parameters: the prefix and the flag resetCount. The second usage invokes the default constructor and set a value for the Prefix public property. Subsequent usages are similar.



Design-time behavior



At design-time the markup extension is already alive and kickin’, as you can see in this screenshot:



markext01



If you close and re-open the designer, the static counts stay alive. As a consequence, at design-time you’ll see that the two Run elements are counted 2 and 3 (and not 0 and 1 as when opening the designer the first time):



markext02



Conclusions



Custom MarkupExtensions represent one of the many extensibility points given by WPF and XAML. In this simple sample I showed the basics about declaring and using a custom markup extension and how to provide transversal capabilities to the XAML elements, thanks to the usage of static members. You can easily find similar usages for any real LOB scenario.

No comments: