XAML Markup Extensions
In a previous post I wrote about how cool XAML Markup Extensions were. As I was writing that post, it occurred to me that I should put together a tutorial on writing one from scratch. So here it is...
Why Are Markup Extensions Important?
All properties in XAML are set using strings - even when the property in question is not a string type.
<Label FontSize="Micro" TextColor="Red" Text="Hello World" />
Neither FontSize
nor TextColor
are typed as strings, but yet I can set them equal to strings in XAML.
That's accomplished because there are several type converters for individual properties that get run automatically when the XAML is being parsed. The converter is able to take the string representation of Red
and turn it into the Color
structure's Red
value.
But what about when there isn't a built-in converter for a property... one that takes a string and moves it to the correct data type? That's where Markup Extensions come in handy, and important.
For example, data binding - or the process of looking up a value from a view model, and then potentially setting that value back to a view model's property - is accomplished through a Markup Extension.
So these are important, and it pays to understand how they work.
What is a Markup Extension
But what exactly is a Markup Extension?
The quick answer ... it's anything with a curly brace being set to a property in XAML. Something like a binding:
<Entry Text="{Binding FirstName}" />
So the Binding
keyword here is the Markup Extension.
And there are several other built-in markup extensions in the Xamarin.Forms flavor of XAML.
- x:Array
- x:Null
- x:Reference
- x:Static
- x:Type
- ConstraintExpression
- DynamicResource
- StaticResource
The ones that start with x:
are all native to the XAML specification, the others were introduced either with WPF or Xamarin.Forms.
OK great - but I still haven't answered the question - what is a markup extension?
A markup extension provides a means to run some code before setting the value on a property in XAML and then return a value with an appropriate type for that property.
How Markup Extensions Work
Markup Extensions adhere to an interface - IMarkupExtension
(I know, you would never have guess that)...
The IMarkupExtension
only has one member to implement:
public object ProvideValue(IServiceProvider serviceProvider)
When the XAML is being parsed, and the compiler comes across a Markup Extension, it is going to invoke that function before setting the property the Markup Extension is on.
By default, there is no input into the ProvideValue
function, in other words from the example above, the IServiceProvider
does not bring in the Micro
or Red
. (And in fact, the IServiceProvider
input comes as a XamlServiceProvider
, which does not have any public APIs to get at, but it's used internally by the XAML compiler for things like ... data binding).
That means to get values into the IMarkupExtension
class we're going to have to use properties.
The good news, is that the properties are the same 'ol properties that we've been using since the dawn of C#!
So, let's build a Markup Extension!
Building a Markup Extension
For this demo, I am going to build a Markup Extension that interprets words as thicknesses - so I can set things like Margin
and Padding
as FatLeft
or SkinnyTop
.
So my class name will be FriendlyThickness
and it'll have a definition like this:
public class FriendlyThickness : IMarkupExtension
It's going to need to take in an input value ... the FatLeft
or SkinnyTop
, so I need to make that a property.
public string FriendlyName { get; set; }
Now there's an attribute in XAML that allows one to specify that a property be the "default" property... in other words, it's the one whose value gets set without having to specify it by name. And that's called ContentProperty
. (And this applies to anything you would build that can be used in XAML.)
I want to use that to make calling this markup extension easier.
It needs to be set at the class level, so the class definition now looks like this:
[ContentProperty("FriendlyName")]
public class FriendlyThickness : IMarkupExtension
Then on to the implementation of the ProvideValue
. In this case, the implementation will be pretty trivial, but it should illustrate the point of what you can do.
public object ProvideValue(IServiceProvider serviceProvider)
{
switch (FriendlyName?.ToLower())
{
case "fatleft":
return new Thickness(50, 0, 0, 0);
case "fatright":
return new Thickness(0, 0, 50, 0);
case "skinnytop":
return new Thickness(0, 10, 0, 0);
case "skinnybottom":
return new Thickness(0, 0, 0, 10);
default:
return new Thickness();
}
}
Possible values that could come in are FatLeft
, FatRight
, SkinnyTop
, SkinnyBottom
- and of course something else or nothing and that would return the default value.
Consuming it is pretty straight forward. First make sure to reference the namespace:
xmlns:local="clr-namespace:MarkupExtensions"
Then use it!
<Label Text="Big Left Margin" Margin="{local:FriendlyThickness FatLeft}" />
Or to specify the FriendlyName
property explicitly:
<Label Text="Left Margin" Margin="{local:FriendlyThickness FriendlyName=FatLeft}" />
And that's all there is to building and using a Markup Extension! The entire source can be found here.
Wrapping Up
Markup Extensions provides a very powerful means to set properties on objects when there is not a built-in converter to move a string representation of the value to the correct type.
A Markup Extension gives the developer a chance to run some code before a value is set on a property, thus all manner of manipulation can be performed - like data binding!
You can also create your own custom Markup Extensions by implementing the IMarkupExtension
interface, just remember that you need to create properties to accept any input.