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.
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.
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.
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.
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";
}
}
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.
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.
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.
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.
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);
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;
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);
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.
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/
.
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;
}
}
It's customary to suffix the name of a markup extension with “Extension”—for example, SimpleMarkup
Extension. 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}" />
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.
MarkupExtensions
in your AdventureWorks project, and add a new class to it named StaticExtension
.using
statements to the top of the file:
using System;
using System.Reflection;
using System.Windows.Markup;
MarkupExtension
:
public class StaticExtension : MarkupExtension
{
}
ProvideValue
method inherited from MarkupExtension
, which contains the logic that needs to be performed, and returns the result:
public override object ProvideValue(IServiceProvider serviceProvider)
{
}
public string Path { get; set; };
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;
}
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;
}
}
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.
GlobalSettings
, and have it expose a static property (or field) named CompanyName
: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.AdventureWorks.MarkupExtensions
namespace:
xmlns:me="clr-namespace:AdventureWorks.MarkupExtensions"
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}" />
<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;
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>