First of all, what is this misterious thing, MarkupExtension? As it follows from it's name, this is some kind of the extension for the XAML markup syntax, and in our case that is an the extension that allows user to shorten or simplify some operations in XAML, or at least provide some non-string data in case TypeConverter can not be used. One of the most common examples of the MarkupExtension is a shortened declaration of a data binding, like {Binding Path}. {StaticResource key} is also widely used, and that is also a MarkupExtension. Both of them can be declared in element syntax, but that is not widely used because the amount of XAML needed in such case is a bit larger, so I will use attribute syntax in my samples.
In general, MarkupExtension is an object that is created by the XAML reader when it finds some construction like {ExtensionName Param1, param2,... } in object's attributes in XAML (or if we use the element syntax for the property values, than xaml reader just searches for the class with the specified name, but with an Extension word at the end). For example, such property value like Property="{Binding Value}" in XAML will be treated like this:
BindingExtension extension = new BindingExtension( Value );
someobj.Property = extension.ProvideValue( ... some service provider here... );
Here is a short sample of a simple markup extension:
| namespace sample |
| { |
| public class TestExtension : MarkupExtension |
| { |
| private string m_text; |
| |
| public TestExtension( string text ) |
| { |
| m_text = text; |
| } |
| |
| public override object ProvideValue( IServiceProvider serviceProvider ) |
| { |
| return m_text; |
| } |
| } |
| } |
An extension from this sample can be used in this way:
| <Window xmlns:local="clr-namespace:sample"> |
| <TextBlock Text="{local:Test text_to_print}"/> |
| Window> |
As you could notice, parameters, that are passed in XAML after the name of the markup extension, are passed as a parameters to the most suitable constructor of the extension class. Also you can assign extension's properties in a way like this: {Extension Property1=data data data, Property2=data1 data1 data1}. All constructor parameters and property setters are separated with commas, so the values for them can contain spaces.
There is one case when the usage of the markup extension with element syntax can be extremely usefull. That is a case when extension should accept unknown count of the data of unknown type, like x:Array
extension does.
Here is how it can be used:
| <x:Array Type="typeName"> |
| <arrayObject1/> |
| <arrayObject2/> |
| ... |
| x:Array> |
You can add support for such functionality into your own markup extension by supporting IAddChild
interface - XAML reader understands that as an ability to contain sub-elements inside the extension object.
So now you know what is a MarkupExtension (you can also look for information about it in MSDN article
). But there are two more things, related to markup extensions, that you do not know about yet: MarkupExtensionReturnType attribute and the list of the services that are provided by the service provider that is passed in a ProvideValue method.
MarkupExtensionReturnType
MarkupExtensionReturnType attribute is used to determine the type of the value that is supposed to be returned by the extension. This is needed just for compilation-time checks and does not have any influence on the runtime.
Here is an example of the usage of the attribute:
| namespace sample |
| { |
| [MarkupExtensionReturnType(typeof(int))] |
| public class TestExtension : MarkupExtension |
| { |
| private string m_text; |
| |
| public TestExtension( string text ) |
| { |
| m_text = text; |
| } |
| |
| public override object ProvideValue( IServiceProvider serviceProvider ) |
| { |
| return int.Parse(m_text); |
| } |
| } |
| } |
Such extension can be used only the property accepts integers as a value.
Services
By default, the following services are supported: IProvideValueTarget, IXamlTypeResolver, IUriContext and IFreezeFreezables.
All of them can be accessed via serviceProvider in a following way: IProvideValueTarget target = (IProvideValueTarget)serviceProvider.GetService( typeof(IProvideValueTarget) );
Here are some details on every of the supported services:
- IProvideValueTarget
Used to provide the information on the target object and target property for the markup extension.
Example:
| IProvideValueTarget target = (IProvideValueTarget)serviceProvider.GetService( typeof(IProvideValueTarget) ); |
| |
| Debug.WriteLine( target.TargetObject, "Target object" ); |
| |
| Debug.WriteLine( target.TargetProperty, "Target property" ); |
- IXamlTypeResolver
Such type resolver is used to find the desired type by it's name. It is used in x:Type
extension to resolve types by their names, like Brush or local:MyType.
Example:
| IXamlTypeResolver service = serviceProvider.GetService( typeof( IXamlTypeResolver ) ) as IXamlTypeResolver; |
| Type type = service.Resolve( "Brush" ); |
- IUriContext
Used to determine the URI of the object, in context of which an extension is used. In most cases that is a path to the XAML file, where the particular instance of the extension is used. For examle it can be pack://application:,,,/App1;component/window1.xaml
Example:
| IUriContext target = (IUriContext)serviceProvider.GetService( typeof( IUriContext ) ); |
| Debug.WriteLine( target.BaseUri ); |
In the next article I will provide some usefull markup extensions that can make the life of the WPF developer a bit easier.
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5