Custom Markup Extensions

You were introduced to markup extensions in Chapter 2, and learned how a markup extension is essentially a class containing some logic that is evaluated at runtime. Examples of markup extensions built into Silverlight include Binding, StaticResource, and x:Null. However, Silverlight is missing a number of the handy markup extensions that are available in WPF, particularly the x:Static markup extension that enabled you to bind to a static property, field, constant, and so forth.

A new feature of Silverlight 5 is the ability to create your own custom markup extensions. This feature provides additional flexibility when writing XAML, and can enable you to implement solutions much more elegantly, with less XAML. Let's look at creating a custom markup extension now.

images Note Although you will probably rarely need to write a custom markup extension, there are numerous scenarios where the ability to do so can be very useful. For example, you could implement a markup extension that returns localized text for labels according to the current (or a selected) culture, bind to XML, simplify binding to configuration settings, or use one to help implement role-based authorization in your application.

Creating a Custom Markup Extension

There are two means of creating custom markup extensions. One is by creating a class that inherits from the MarkupExtension class, and the other is by implementing the IMarkupExtension<T> interface. Both are found in the System.Windows.Markup namespace.

Inheriting from MarkupExtension

You can create a custom markup extension by simply creating a class that inherits from MarkupExtension, in the System.Windows.Markup namespace, and overrides its ProvideValue method, which returns the result. For example, here is a simple markup extension using this method, which returns TEST as a string:

using System;
using System.Windows.Markup;

public class SimpleMarkupExtension : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return "TEST";
    }
}

This is hardly a very useful markup extension, but it demonstrates the concept. You can put your own logic in the ProvideValue method, and return that as the result instead. You can also add properties to the markup extension, and assign values to these in XAML when the markup extension is used. You can then use these values in the logic in the ProvideValue method.

Implementing IMarkupExtension<T>

Usually, you will inherit from the MarkupExtension class when creating a custom markup extension, but if you want your markup extension to inherit from another class—for example, the Binding markup extension inherits from BindingBase—you're out of luck because the .NET CLR doesn't support multiple inheritance. In addition, you'll have noticed that the ProvideValue method from the previous method returns an object, meaning that the value being returned is untyped.

You can solve both of these problems by having your custom markup extension class implement the IMarkupExtension<T> interface instead of inheriting from MarkupExtension. Here is the previous example, but implementing the IMarkupExtension<T> interface, returning the result typed as a string:

using System;
using System.Windows.Markup;

public class SimpleMarkupExtension : IMarkupExtension<string>
{
    public string ProvideValue(IServiceProvider serviceProvider)
    {
        return "TEST";
    }
}

images Note The MarkupExtension class actually implements the IMarkupExtension<object> interface.

Although we won't discuss dependency properties until Chapter 12, implementing this interface allows your markup extension to inherit from DependencyObject, enabling it to host dependency properties—something you can't do when your markup extension inherits from the MarkupExtension class because it doesn't inherit from DependencyObject. If you want your markup extension to have properties that can accept a markup extension as their value, those properties need to be dependency properties and, thus, your markup extension needs to implement this interface.

images Note A feature of custom markup extensions in WPF is that they can have a constructor that takes a single value, also known as a positional parameter. In Silverlight, you can see how the Binding markup extension uses this feature to allow you to pass it the path to bind to without needing to specify the Path property name—for example, {Binding FirstName} instead of {Binding Path=FirstName}. Unfortunately, there is no way to implement this same behavior for your custom markup extensions in Silverlight.

Extending the Binding Markup Extension

You can override the Binding markup extension and customize it to your needs, although there are a number of limitations when doing so. For example, if you set the same property values on bindings regularly, such as its Mode or Converter properties, you can override the Binding class and set property values in its constructor, reducing the verbosity of your XAML. For example, instead of setting the Mode property to TwoWay on each use, you can create a custom Binding class that has this value set by default:

public class TwoWayBinding : Binding
{
    public TwoWayBinding() : base()
    {
        Mode = BindingMode.TwoWay;
    }
}

There are limitations you'll face when doing this, however. You can't override the Binding class's ProvideValue method, nor will you be able to exclude the Path= when making use of the custom binding; its positional parameter will no longer work. However, it can make your XAML less verbose, especially if you use it to configure a value converter for the binding.

Services

You might have noticed that the ProvideValue method takes in an IServiceProvider parameter. A service provider is passed in via this parameter, which provides access to services available to the markup extension, giving it some context. You can call the GetService method on the service provider object, passing it the type of interface representing the service you want returned. Let's take a look at the key services you might want to get a reference to, and why.

IProvideValueTarget

Most commonly, you will use the GetService method on the service provider object to get the IProvideValueTarget service. This service returns details of the object/control and property that the markup extension is applied to.

Let's say you have the custom markup extension we created earlier, named SimpleMarkupExtension, and applied it to the Text property of a TextBox control:

<TextBox Text="{local:SimpleMarkupExtension}" />

You can get a reference to the TextBox control and the Text property that it's applied to from the TargetObject and TargetProperty properties on the IProvideValueTarget service:

IProvideValueTarget targetService =
    serviceProvider.GetService(typeof(IProvideValueTarget)) as
                                                          IProvideValueTarget;

DependencyObject control = targetService.TargetObject as DependencyObject;
PropertyInfo property = targetService.TargetProperty as PropertyInfo;

So what uses could you have for this information? Well, most commonly, you would use this information to set the value of the property of the control at a later stage. An important point to note when creating custom markup extensions is that the ProvideValue method is only ever called once on a markup extension. Therefore, if you need to update the target property that the markup extension is applied to at a later stage, you'll need to cache this information to use then. For example, you might want to update the target property in response to something changing in the code-behind, such as an event being raised. Alternatively, the value that the target property should have might not be able to be determined at the time that the ProvideValue method is called. In that case, you can return a default/temporary value and update the target property when the value becomes available.

By providing a reference to the target object/control, you can also apply values to other properties on the object/control. For example, you might want to disable it by setting its IsEnabled property to false, hide it by setting its Visibility property to Collapsed, change its background in response to an invalid value, and so on.

You can assign a new value to the target property using the following line of code:

property.SetValue(control, value, null);
IRootObjectProvider

If you need to get a reference to the root control in your view, such as the UserControl or Page, you can use the IRootObjectProvider service to get this for you, like so:

IRootObjectProvider rootService =
    serviceProvider.GetService(typeof(IRootObjectProvider))
                                                       as IRootObjectProvider;

object root = rootService.RootObject;
IXamlTypeResolver

The IXamlTypeResolver service resolves types referenced in XAML to CLR types. For example, say you add a property to your custom markup extension that takes in the name of a type as a string:

public string MyType { get; set; }

You also have a class named SomeCLRClass in your project—for this example, it will be in the same namespace as your custom markup extension:

public class SomeCLRClass
{

}

Now when using the custom markup extension in your XAML, you assign the name of the SomeCLRClass class to the MyType property on your markup extension:

<TextBox Text="{local:SimpleMarkupExtension MyType=local:SomeCLRClass}" />

In the ProvideValue method of your custom markup extension, you can now convert this type passed in from the XAML to an actual type that you can use in your logic:

IXamlTypeResolver typeResolverService =
    serviceProvider.GetService(typeof(IXamlTypeResolver))
                                                      as IXamlTypeResolver;

Type type = typeResolverService.Resolve(MyType);

Advanced Markup Extension Behavior

On the surface, creating custom markup extensions seems like an easy process, and for the most part, that is indeed the case. However, you can use markup extensions in ways that might not seem immediately obvious. Let's take a bit of a deeper look at these advanced uses and behaviors.

Selecting Event Handlers Using Custom Markup Extensions

You might not have considered it initially, but you can actually assign a markup extension to an event and return an event handler. This can be useful to allow a handler to be chosen to handle the event at runtime. Another potential use is as a proxy that calls a method on a view model when the event is raised. As you'll see in Chapter 13, generally you will call a method on a view model, when using the MVVM design pattern, by using a command or behavior. You could also write a custom markup extension that performs the same task. Sergey Barskiy has a blog post covering a markup extension that he wrote to do just this, at http://dotnetspeak.com/index.php/2011/04/more-on-markup-extensions-in-silverlight-5/.

Custom Markup Extensions That Return Another Markup Extension

Your custom markup extension can actually return another markup extension as its value. This markup extension will then be evaluated to obtain the value to be assigned to the property that the custom markup extension is applied to. For example, you can have a custom markup extension that returns a binding:

public class ReturnBindingMarkupExtension : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        Binding binding = new Binding("FirstName");
        binding.Mode = BindingMode.TwoWay;
        return binding;
    }
}
Excluding “Extension” from a Markup Extension's Name When Using It

It's customary to suffix the name of a markup extension with “Extension”—for example, SimpleMarkupExtension. Note, however, that when using the custom markup extension, you don't have to include this Extension suffix when using it, in much the same way that you can exclude the Attribute suffix of an attribute's name when decorating classes, properties, etc. with it. If you name your custom markup extension SimpleMarkupExtension, you can actually reference it in your XAML like so:

<TextBox Text="{local:SimpleMarkup}" />

images Workshop: Implementing the Custom Markup Extension

Let's implement a custom markup extension that returns the value of a static field/property, in much the same way x:Static does in WPF.

Part 1: Creating the Custom Markup Extension
  1. Create a new folder named MarkupExtensions in your AdventureWorks project, and add a new class to it named StaticExtension.
  2. Add the following using statements to the top of the file:
    using System;
    using System.Reflection;
    using System.Windows.Markup;
  3. Have your class inherit from MarkupExtension:
    public class StaticExtension : MarkupExtension
    {

    }
  4. Now you can override the ProvideValue method inherited from MarkupExtension, which contains the logic that needs to be performed, and returns the result:
    public override object ProvideValue(IServiceProvider serviceProvider)
    {

    }
  5. However, to implement our logic, we first need a property that provides the path of the static field/property that the markup extension will get the value of:
    public string Path { get; set; };
  6. Now we can actually implement our logic in the ProvideValue method:
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        object result = null;

        if (Path != null && Path.IndexOf('.') != 0)
        {
            // Split into qualified type and property name
            string typeName = Path.Substring(0, Path.LastIndexOf('.'));
            string propertyName = Path.Substring(Path.LastIndexOf('.') + 1);

            Type type = Type.GetType(typeName, false, false);

            if (type != null)
            {
                FieldInfo field = type.GetField(propertyName,
                    BindingFlags.Public | BindingFlags.Static);

                if (field != null)
                {
                    result = field.GetValue(null);
                }
                else
                {
                    PropertyInfo property = type.GetProperty(propertyName,
                        BindingFlags.Public | BindingFlags.Static);

                    if (property != null)
                        result = property.GetValue(null, null);
                }
            }
        }

        return result;
    }
  7. The complete markup extension class should look like this:
    using System;
    using System.Reflection;
    using System.Windows.Markup;

    namespace AdventureWorks.MarkupExtensions
    {
        public class StaticExtension : MarkupExtension
        {
            object result = null;

            if (Path != null && Path.IndexOf('.') != 0)
            {
                // Split into qualified type and property name
                string typeName = Path.Substring(0, Path.LastIndexOf('.'));
                string propertyName = Path.Substring(Path.LastIndexOf('.') + 1);

                Type type = Type.GetType(typeName, false, false);

                if (type != null)
                {
                    FieldInfo field = type.GetField(propertyName,
                        BindingFlags.Public | BindingFlags.Static);

                    if (field != null)
                    {
                        result = field.GetValue(null);
                    }
                    else
                    {
                        PropertyInfo property = type.GetProperty(propertyName,
                            BindingFlags.Public | BindingFlags.Static);

                        if (property != null)
                            result = property.GetValue(null, null);
                    }
                }
            }

            return result;
        }
    }

images Note This extension will get only the value of the static field/property; it won't update it. Nor will it respond to any changes in the value of the static field/property. As an exercise, try extending this markup extension to respond to changes in the value of the static field/property. Start by checking to see whether the “bound” type implements INotifyPropertyChanged, and if so, handle its PropertyChanged event to check for changes in the property value. Assuming you have cached the target object/control and target property, retrieved by using the IProvideValueTarget service, you can then update its value accordingly. Another exercise you can try is updating the source field/property in response to changes to the target. For this case, the markup extension will need to add an event handler to the target control, handling the LostFocus, TextChanged, or other appropriate event, and update the value of the source field/property in response.

Part 2: Using the Custom Markup Extension
  1. First, we need a static field/property that our markup extension will obtain the value of. Add a class to your project's root named GlobalSettings, and have it expose a static property (or field) named CompanyName:
    namespace AdventureWorks
    {
        public class GlobalSettings
        {
            public static string CompanyName
            {
                get
                {
                    // Hardcoded value for demonstration purposes
                    return "Apress";
                }
            }
        }
    }
  2. For testing purposes, add a new view named TestExtensionView to the Views folder in your application in order, and hook it up to the menu so that you can navigate to it, as was demonstrated in Chapter 3.
  3. Add a namespace prefix declaration to the view, pointing to the AdventureWorks.MarkupExtensions namespace:
    xmlns:me="clr-namespace:AdventureWorks.MarkupExtensions"
  4. Add a TextBlock control to your view, and assign the markup extension to its Text property, pointing the Path of the markup extension to the static CompanyName property on the GlobalSettings class:
    <TextBlock
            Text="{me:Static Path=AdventureWorks.GlobalSettings.CompanyName}" />
  5. The full XAML for the view should look something like this:
    <navigation:Page
      x:Class="AdventureWorks.Home"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:navigation="clr-namespace:System.Windows.Controls; images
                        assembly=System.Windows.Controls.Navigation"
      xmlns:me="clr-namespace:AdventureWorks.MarkupExtensions"
      mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
      Style="{StaticResource PageStyle}">

        <Grid x:Name="LayoutRoot">
            <TextBlock
              Text="{me:Static Path=AdventureWorks.GlobalSettings.CompanyName}" />
        </Grid>
    </navigation:Page>
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset