Creating a Custom Markup Extension in WPF



Markup extensions are placeholders in XAML that are used to resolve a property at runtime. A markup extension enables you to extend XAML and set any property that can be set in XAML using attribute syntax. xNull, x:Array, :StaticResource, and DynamicResource are some common markup extensions.

The System.Windows.Markup namespace defines many of the markup extension classes. These class names end with the suffix Extension; however, you can omit the Extension suffix. For example, in XAML, you represent the NullExtension markup class as x:Null.

A custom markup extension is a class created by extending the MarkupExtension class or the IMarkupExtension interface. It is useful in scenarios where you need to provide functionality or behavior that is beyond the scope of existing built-in markup extensions.

Consider that you want to bind a ListBox that will bind to XML data as shown below but for some reason you don't want to use an ObjectDataProvider.

<ListBox ItemsSource="<some way to bind the data> Source=Books.xml, Path=/Book/Title}"

If you want to declaratively bind as shown above, you will need to use a custom markup extension.

The steps to create and use such an extension are:

  1. Create a WPF application named WPFXMLBinding
  2. Add an XML file named Books to the application that has the following contents:

    <?xml version="1.0" encoding="utf-8" ?>
    <Books>
      <
    Book Title="Coma" Author="Robin Cook" />
      <Book Title="The Color Purple" Author="Alice Walker" />
      <Book Title="The White Tiger" Author="Aravind Adiga" />
      <Book Title="A Thousand Splendid Suns" Author="Khaled Hoseini" />
    </Books>
     
  3. Add reference to System.Xml.Linq assembly.
  4. Using the Project->Add Class menu option, add a class named CustomXMLExtension to the application.
  5. Add the following code to the class:

    namespace WPFXMLBinding
    {
        public class
    CustomXMLExtension
    : MarkupExtension
        {
            public string Source { get; set; }
            public string Path { get; set; }
            private static List<string> Parse(string file, string path)
            {
                XDocument xdoc = XDocument.Load(file);

                string[] text = path.Substring(1).Split('/');
                string desc = text[0].ToString();
                string elementname = text[1].ToString();
                List<string> data = new List<string>();

                IEnumerable<XElement> elems = xdoc.Descendants(desc);
     
                IEnumerable<XElement> elem_list = from elem in elems
                                                  select elem;
                foreach (XElement element in elem_list)
                {
                    String str0 =
                    element.Attribute(elementname).Value.ToString();
                    data.Add(str0);
                }
                return data;
            }
            /// <summary>
            /// Overridden method, returns the source and path to bind to
            /// </summary>
            /// <param name="serviceProvider"></param>
            /// <returns></returns>
            public override object ProvideValue(IServiceProvider
            serviceProvider)
            {
                if ((Source != null) && (Path != null))
                    return Parse(Source, Path);
                else throw new InvalidOperationException("Inputs cannot be blank");
            }
        }
    }

    The CustomXMLExtension class is derived from the MarkupExtension class which is defined in the System.Windows.Markup assembly.

    The MarkupExtension class provides a base class for custom XAML markup extension implementations.
    This class defines a method named ProvideValue() whose syntax is as follows:

    Syntax:

    public abstract Object ProvideValue
    (
    IServiceProvider serviceProvider
    )

    The ProvideValue() method is overridden (or implemented, if inheriting from the interface) in the derived class) and returns an object. This object will become the value of the target property for the markup extension. In the current example, the ProvideValue() method will return an object used as the source for the XML binding.
     

  6. In the MainWindow.xaml, write the XAML markup as shown:

    <Window x:Class="WPFXMLBinding.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525"
    xmlns:local="clr-namespace:WPFXMLBinding">
    <
    Grid x:Name="LayoutRoot" Background="White">
    <ListBox ItemsSource="{local:CustomXMLExtension Source=Books.xml, Path=/Book/Title}" Height="200"
    Width="200" Background
    ="LightSalmon">
    </ListBox>
    </
    Grid>
    </
    Window>

The XAML markup shown here invokes the custom markup extension and passes the XML file name and path to bind to using the Source and Path attributes as shown below:

<ListBox ItemsSource="{local:CustomXMLExtension Source=Employee.xml,
Path=/Manager/FirstName}" Height="200" Width="200" Background="Beige" />

This makes declarative XML binding possible and as well as easier to work with.  And that's it! Now you' re ready to go. To build and execute, I mean.

The output of the above example will be as shown in Figure 1.

customWPF.bmp

Figure 1

In this manner, you can create any custom markup extension in WPF and use it in your applications.

Conclusion: In this article, you have learned how to create and use a custom markup extension.