XAML as an Extensible Markup Language has great flexibilities to create objects in XAML itself and do functions like automatic binding, Conversion of Data, etc. Markup Extension allows you to truly extend your markup upto a certain extent to elevate you to write less code and design your application with richer extent.
In this article I will discuss how to use existing TypeConverters and MarkupExtensions and also will show you how to create custom implementation of both of them.
Introduction
In my previous article I have discussed about basic architecture of WPF, and then gradually started learning with Layout panels, Transformation, introduced different
Controls,
containers, UI
Transformation etc. In this article I will discuss the most important part of XAML which every developer must learn before starting with any XAML applications.
Markup Extensions are extensions to XAML which you might use to assign custom rules on your XAML based Applications. Thus any custom behavior which you would like to impose on your application in your designer, you should always use Markup Extensions. Here we will discuss how you can use Markup Extensions to generate your custom behaviors to XAML.
XAML or
Extensible Application Markup Language is actually an XML format which has special schema defined. Now you might always wonder, how extensible the Markup is. What type of capabilities that are there within XAML which widely differs the XAML with XML. Yes, it is because of XAML parser which has huge number of capabilities to make the normal XML to a very rich UI designs.
You all know XAML is actually a text format. The tags are very same as with any XML where the attributes takes everything as String. Even though you want to assign a object to a string, you cannot do so because the object can only take a string. Markup Extension allows you to handle these kind of situations. So you can say a Markup extension is actually the way to extend a normal XML to a complete
Extensible Markup as XAML.
As XAML takes everything as string, sometimes we need to convert those data into valid values. For instance, when we use Margin, we need to specify each values of margin element. In such situation where the conversion is very simple and straight forward, we can use Type Converters to do this job rather than going for Markup extensions. Lets discuss about Type Converters first before we move to Markup Extensions.
Type Converter
As I already told you, markup as an extension of XML cannot impose restriction over a data element. That means we can only specify string data for attributes of any object in XAML. But XAML provides a flexibility to create your Type converter which allows you to impose restriction on the data. Thus even primitives like
Single or
Double could not have restrictions while you describe in your XAML. Type Converters plays a vital role to put this restriction to XAML parser.
XAML parser while parsing any value of an attribute needs two pieces of information.
1.
Value Type : This determines the Type to which the string data should be converted to.
2.
Actual Value Well, when parser finds a data within an attribute, it first looks at the type. If the type is primitive, the parser tries a direct conversion. On the other hand, if it is an
Enumerable, it tries to convert it to a particular value of an Enumerable. If neither of them satisfies the data, it finally tries to find appropriate Type
Converters class, and converts it to an appropriate type. There are lots of
typeconverters already defined in XAML, like Margin.
Margin = 10,20,0,30 means margin :
left,top,right,bottom is defined in sequence. Therefore system defines a typeconverter that converts this data into Thickness object.
Custom TypeConverter
To create a TypeConverter we need to decorate the Type with
TypeConverterAttribute and define a custom class which converts the data into the actual type. And the actual converter class, a class which implements from
TypeConverter.
Let us make it clear using an Example :
TypeConverter to convert a GeoPoint:
To create a
TypeConverter as I have already told you, you need to create a class for which the
TypeConverter to be applied. In my example, I have created a class which has two properties called
Latitude and
Longitude and creates an implementation of a Geographic Point. Lets see how the class looks like :
[global::System.ComponentModel.TypeConverter(typeof(GeoPointConverter))]
public class GeoPointItem
{
public double Latitude { get; set; }
public double Longitude { get; set; }
public GeoPointItem()
{
}
public GeoPointItem(double lat, double lon)
{
this.Latitude = lat;
this.Longitude = lon;
}
public static GeoPointItem Parse(string data)
{
if (string.IsNullOrEmpty(data)) return new GeoPointItem();
string[] items = data.Split(',');
if (items.Count() != 2)
throw new FormatException("GeoPoint should have both latitude and longitude");
double lat, lon;
try
{
lat = Convert.ToDouble(items[0]);
}
catch (Exception ex) {
throw new FormatException("Latitude value cannot be converted", ex);
}
try
{
lon = Convert.ToDouble(items[1]);
}
catch (Exception ex) {
throw new FormatException("Longitude value cannot be converted", ex);
}
return new GeoPointItem(lat, lon);
}
public override string ToString()
{
return string.Format("{0},{1}", this.Latitude, this.Longitude);
}
}
In the above code you can see that I have created a quite normal class, which defines a Geographic point on Earth. The type has two parameters,
Latitude and
Longitude and both of which are
Double values. I have also overridden the
ToString() method, which is actually very important in this situation to get the string resultant of the object. The Parse method is used to parse a string format to GeoPointItem.
After implementing this, the first thing that you need to do is to decorate the class with
TypeConverter Attribute. This attribute makes sure that the item is Converted easily using the
TypeConverter GeoPointConverter passed as argument to the attribute. Thus while XAML parser parses the string, it will call the
GeoPointConverter automatically to convert back the value appropriately.
After we are done with this, we need to create the actual converter. Lets look into it :
public class GeoPointConverter : global::System.ComponentModel.TypeConverter
{
//should return true if sourcetype is string
public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType)
{
if (sourceType is string)
return true;
return base.CanConvertFrom(context, sourceType);
}
//should return true when destinationtype if GeopointItem
public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, Type destinationType)
{
if (destinationType is string)
return true;
return base.CanConvertTo(context, destinationType);
}
//Actual convertion from string to GeoPointItem
public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
try
{
return GeoPointItem.Parse(value as string);
}
catch (Exception ex)
{
throw new Exception(string.Format("Cannot convert '{0}' ({1}) because {2}", value, value.GetType(), ex.Message), ex);
}
}
return base.ConvertFrom(context, culture, value);
}
//Actual convertion from GeoPointItem to string
public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if(destinationType == null)
throw new ArgumentNullException("destinationType");
GeoPointItem gpoint = value as GeoPointItem;
if(gpoint != null)
if (this.CanConvertTo(context, destinationType))
return gpoint.ToString();
return base.ConvertTo(context, culture, value, destinationType);
}
}
In the above code we have implemented the converter class by deriving it from
TypeConverter. After implementing it from TypeConverter class we need to override few methods which XAML parser calls and make appropriate modifications so that the XAML parser gets the actual value whenever required.
1.
CanConvertFrom : This will be called when XAML parser tries to parse a string into a GeopointItem value. When it returns true, it calls ConvertFrom to do actual Conversion.
2.
CanConvertTo : This will be called when XAML parser tries to parse a GeoPointItem variable to a string equivalent.When it returns true, it calls ConvertTo to do actual Conversion.
3.
ConvertFrom :Does actual conversion and returns the GeoPointItem after successful conversion.
4.
ConvertTo : Does actual conversion and returns the string equivalent of GeoPointItem passed in.
In the above example, you can see I have actually converter the string value to GeoPointItem and vice-versa using the TypeConverter class.
Now its time to use it. To do this I build a custom UserControl and put the property of that to have a
GeoPoint.
The XAML looks very simple :
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Latitude" Grid.Row="0" Grid.Column="0"></TextBlock>
<TextBox x:Name="txtlat" MinWidth="40" Grid.Row="0" Grid.Column="1" TextChanged="txtlat_TextChanged"/>
<TextBlock Text="Longitude" Grid.Row="1" Grid.Column="0"></TextBlock>
<TextBox x:Name="txtlon" MinWidth="40" Grid.Row="1" Grid.Column="1" TextChanged="txtlon_TextChanged"/>
</Grid>
It has 2
Textboxes which displays value of
Latutude and
Longitude individually. And when the value of these
textboxes are modified, the actual value of the
GeopointItem is modified.
public partial class GeoPoint : UserControl
{
public static readonly DependencyProperty GeoPointValueProperty = DependencyProperty.Register("GeoPointValue", typeof(GeoPointItem), typeof(GeoPoint), new PropertyMetadata(new GeoPointItem(0.0, 0.0)));
public GeoPoint()
{
InitializeComponent();
}
public GeoPointItem GeoPointValue
{
get
{
return this.GetValue(GeoPointValueProperty) as GeoPointItem;
}
set
{
this.SetValue(GeoPointValueProperty, value);
}
}
private void txtlat_TextChanged(object sender, TextChangedEventArgs e)
{
GeoPointItem item = this.GeoPointValue;
item.Latitude = Convert.ToDouble(txtlat.Text);
this.GeoPointValue = item;
}
private void txtlon_TextChanged(object sender, TextChangedEventArgs e)
{
GeoPointItem item = this.GeoPointValue;
item.Longitude = Convert.ToDouble(txtlon.Text);
this.GeoPointValue = item;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
GeoPointItem item = this.GeoPointValue;
this.txtlat.Text = item.Latitude.ToString();
this.txtlon.Text = item.Longitude.ToString();
}
}
Here when the UserControl Loads, it first loads values passed in to the TextBoxes. The TextChanged operation is handled to ensure the actual object is modified whenever the value of the textbox is modified.
From the window, we need to create an object of the UserControl and pass the value as under:
<converter:GeoPoint x:Name="cGeoPoint" GeoPointValue="60.5,20.5" />
The converter points to the namespace. Hence you can see the value is shown correctly in the TextBoxes.
Markup Extension
Now as you know about
TypeConverter, let us take
MarkupExtension into account. Markup Extensions gives the flexibility to create custom objects into your XAML attributes. Every markup extension is enclosed within a {} braces. Thus anything written within the Curl braces will be taken as Markup Extension. Thus XAML parser treats anything within the curl braces not as literal string and rather it tries to find the actual
MarkupExtension corresponding to the name specified within the Curl braces.
If you want to put braces within a string you need to put {} at the beginning to make it an exception.
You can read
"How to Escape {} in XAML" for more information.
Example of Markup Extension :
<TextBox Text={x:Null} />
Probably the simplest of all, it actually a MarkupExtension which returns Null to the Text string.
There are already few Markup Extensions defined with XAML defined within System.Windows.Markup namespace which helps every time to produce functional features within your XAML. Let us discuss few of them :
NullExtension
This is the most simple
MarkupExtension we ever have. It actually returns Null when placed against a value.
Example :
Content = "{x:Null}"
ArrayExtension
This is used to create an
ArrayList of items. The
x:Array actually returns an object of Array of type specified.
Values = {x:Array Type=sys:String}
StaticExtension
Another simple Extension which returns
Static field or Property references.
Text="{x:Static Member=local:MyClass.StaticProperty}"
Thus when you define a static property
StaticProperty for
MyClass, you will automatically set the value to Text property using this.
TypeExtension
Type extension is used to get Types from objects. When the attribute of a control takes the Type object you can use it.
TargetType="{x:Type Button}"
So
TargetType receives a
Type object of Button.
Reference
It is used to Refer an object declared somewhere in XAML by its name. It is introduced with .NET 4.0
Text="{x:Reference Name=Myobject}"
StaticResourceExtension
Resources are objects that are defined within the XAML.
StaticResource substitutes the key assigned to the object and replaces its reference to the Resource element declared.
<Grid.Resources>
<Color x:Key="rKeyBlack">Black</Color>
<SolidColorBrush Color="{StaticResource rKeyBlack}" x:Key="rKeyBlackBrush"/>
</Grid.Resources>
<TextBlock Background="{StaticResource ResourceKey=rKeyBlackBrush}" />
StaticResource errors out if the key is not there during compile time.
DynamicResourceExtension
This is same as StaticResource, but it defers the value to be a runtime reference. Thus you can declare any key for a
DynamicResource and it should be present during runtime when the actual object is created.
<TextBlock Background="{DynamicResource ResourceKey=rKeyBlackBrush}" />
Now the
rKeyBlackBrush if not declared as
Grid.Resources will not error out. You can add that during Runtime when Window Loads.
What are Resources ?
Resources are objects that are created the object is rendered by XAML parser. Every
FrameworkElement object has a
ResourceDictionary object placed within it. You can add Resources within this
ResourceDictionary and can reuse those component within the scope.
Resources are reusable component which one can use for several times to produce its output. So if you need an object that you want to reuse more than once in your application, and you don't want the object to be changed within the scope, Resources are then the best option for you.
<Grid.Resources>
<Color x:Key="rKeyRed">Red</Color>
<Color x:Key="rKeyCyan">Cyan</Color>
<LinearGradientBrush x:Key="rKeyforegroundGradient">
<GradientStop Color="{StaticResource rKeyRed}" Offset="0"></GradientStop>
<GradientStop Color="{StaticResource rKeyCyan}" Offset="1"></GradientStop>
</LinearGradientBrush>
</Grid.Resources>
<TextBlock Text="{Binding ElementName=cGeoPoint, Path=GeoPointValue}" FontSize="30" Margin="50" Grid.Row="1" Grid.ColumnSpan="2" Foreground="{StaticResource rKeyforegroundGradient}" />
So you can see we have defined a
LinearGradientBrush and used as
Foreground of the
TextBlock. This object can be reused any time when required.
Every
FrameworkElement has a property called Resource which takes an object of
ResourceDictionary. You can assign various resources to this Collection which you would use in different object created within the scope of the object. In XAML, resources are declared using x:Key attribute, and later this key can be used to refer to the resource in
ResourceDictionary using
StaticResource or
DynamicResource.
Difference between StaticResource and DynamicResource
StaticResource finds the key defined within the
ResourceDictionary under its scope during the Loading of the application. Hence the Compiler will throw error during compilation if not found in resources. On the other hand,
DynamicResource Markup Extension defers the resource assignment to the actual runtime of the application. So the expression remains unevaluated until the object being created.
Note : If the resource is derived from
Freezable (immutable), any change to the object will change the UI regardless of whether it is a
StaticResource or
DynamicResource. Example :
Brush, Animation, Geometry objects are
Freezable.
Choice between StaticResource and DynamicResource
- StaticResource requires less CPU during runtime, so it is faster.
- StaticResource are created when the application loads. So making everything as StaticResource means slowing down the Application Load process.
- When the resources are unknown during compilation, you can use DynamicResource. DynamicResource are used when user interaction changes the look and feel of an object.
- DynamicResource is best when you want your resource to be pluggable. You can read how to create pluggable resources from : Pluggable styles and Resources
Binding
Binding is the most important and complex Markup extension which might be used to bind one object. It provides Databound object when the data object is assigned to the
DataContext of the object.
Thus say you have an object Obj which has several properties like
Name, Age etc. Then you might write
Text = "{Binding Name}"
which means the DataContext object will automatically be evaluated during runtime and the actual value of Name property of the object will be shown on the
Text property.
Binding has lots of flexibilities, thus you can specify expressions on the databound objects and hence made it one of the most complex Markup extension.
<StackPanel Orientation="Vertical">
<TextBox x:Name="txtObj"></TextBox>
<TextBox x:Name="txtCustom" Text="{Binding FallbackValue=10, ElementName=txtObj, Path=Text,StringFormat='Text Entered : {0:N2}'}, Mode=TwoWay">
</TextBox>
</StackPanel>
As both of them are bound any change to the property automatically reflects the changes to the other textbox. There are few modes of these binding,
OneTime, OneWayToSource, OneWay and TwoWay.
StringFormat creates formatting of the string. We can have
Converters associated with a binding to enable us to return appropriate value based on the value we feed in.
You can read how to create Converter for Binding from :
How to deal with Converter in BindingIn case of binding, you must also make sure that the object which is bound to have implemented
INotifyPropertyChanged. Otherwise every binding will work as OneTime.
You can read more on how you can implement Binding with INotifyPropertyChanged from :
Change Notification for objects and CollectionWe will discuss Binding in greater detail in next article.
RelativeSource
RelativeSource is a MarkupExtension which you have to use within Binding. Binding has a property called
RelativeSource, where you can specify a RelativeSource MarkupExtension. This Markup Extension allows you to give Relative reference to the element rather than absolutely specifying the value.
RelativeSource comes handy when absolute Reference is unavailable.
RelativeSource has few properties which one can use :
- AncestorType : It defines the Type of the Ancestor element where the property to be found. So if you defined a button within a Grid, and you want to refer to that Grid, you might go for RelativeSource.
- Mode : Determines how the RelativeSource to be found. The enumeration has few values :
- Self : This means the Binding will take place within the object itself. So if you want the Background and Foreground of the same object, RelativeSource Mode =Self will come handy.
- FindAncestor : Finds elements parent to it. Thus it will look through all the visual element into the Visual tree and try to find the control for which the AncestorType is provided and it goes on until the object is found in the Ancestor elements to it.
- TemplatedParent : TemplatedParent allows you to find the value from the object for which the Template is defined. Templates are definition of the control itself which helps to redefine the Data and Control Elements altogether. TemplatedParent allows you to find the Templated object directly.
- PreviousData : This allows you to track the Previous Data element when the object is bound to a CollectionView. Thus this is actually RelativeSource to previous DataElement.
- AncestorLevel : Numeric value that determines how many levels that the RelativeSource should search before determining the result. If you specify 1 as AncestorLevel, it will go through only one level.
You can use RelativeSource for any binding.
<Grid Name="grd">
<TextBox x:Name="txtCustom" Text="{Binding Name,
RelativeSource={RelativeSource AncestorType={x:Type Grid},
Mode=FindAncestor,AncestorLevel=2}}" />
</Grid>
This allows you to find the Grid. Practically speaking, this example doesnt gives you a sense when to use RelativeSource as in this case you can easily get the actual reference to the Grid. RelativeSource comes very useful when the Grid is somewhat inaccessible from it, say the TextBox is within the DataTemplate of a Grid.
TemplateBinding
TemplateBinding allows you to bind the value of a property of an object within the Template of a control to the object
TemplatedParent to it.
<RadioButton Foreground="Red">
<RadioButton.Template>
<ControlTemplate>
<ToggleButton Content="{TemplateBinding Foreground}" />
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
In this case the
ToggleButton Caption will be shown as
#FFFF0000 which is the equivalent color for
RadioButton.
MultiBinding
MultiBinding allows you to create Binding based on more than one Binding. You can create a class from
IMultiValueConverter which will allow you to covert multipleBinding statements into one single output.
The only difference between a normal converter and
MultiValueConverter is a normal
IValueConverter takes one value as argument whereas a MultiValueConverter takes an Array of values from all the Binding elements.
We will discuss more on MultiBinding later in the series.
<Button x:Name="NextImageButton" >
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource SelectedItemIndexIsNotLastToBoolean}">
<Binding Mode="OneWay" ElementName="ImageListBox" Path="SelectedIndex" />
<Binding Mode="OneWay" ElementName="ImageListBox" Path="Items.Count" />
</MultiBinding>
</Button.IsEnabled>
<Image Source="Icons/navigate_right.png"/>
</Button>
Custom Markup Extension
In the final section, I will build my custom Markup Extension and use it. For simplicity we use Reflection to Get fields, Methods, and Properties and bind them into a ListBox.
To create a custom Markup Extension, you need to create a class and inherit it from
MarkupExtension. This class has an abstract method called
ProvideValue which you need to override to make the
MarkupExtension work.
So actually the XAML parser calls this
ProvideValue to get the output from
MarkupExtension. So the actual implementation looks like :
public class ReflectionExtension : global::System.Windows.Markup.MarkupExtension
{
public Type CurrentType { get; set; }
public bool IncludeMethods { get; set; }
public bool IncludeFields { get; set; }
public bool IncludeEvents { get; set; }
public ReflectionExtension(Type currentType)
{
this.CurrentType = currentType;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (this.CurrentType == null)
throw new ArgumentException("Type argument is not specified");
ObservableCollection<string> collection = new ObservableCollection<string>();
foreach(PropertyInfo p in this.CurrentType.GetProperties())
collection.Add(string.Format("Property : {0}", p.Name));
if(this.IncludeMethods)
foreach(MethodInfo m in this.CurrentType.GetMethods())
collection.Add(string.Format("Method : {0} with {1} argument(s)", m.Name, m.GetParameters().Count()));
if(this.IncludeFields)
foreach(FieldInfo f in this.CurrentType.GetFields())
collection.Add(string.Format("Field : {0}", f.Name));
if(this.IncludeEvents)
foreach(EventInfo e in this.CurrentType.GetEvents())
collection.Add(string.Format("Events : {0}", e.Name));
return collection;
}
}
You can see the constructor for this class takes one argument of Type. Now to use it we refer it in XAML and use it in similar fashion as we do with other MarkupExtensions.
<ListBox ItemsSource="{local:Reflection {x:Type Grid}, IncludeMethods=true, IncludeFields=true, IncludeEvents=true}" MaxHeight="200" Grid.Row="3" Grid.ColumnSpan="2" />
So here the
Constructor gets argument from {x:Type Grid}. The first argument for any
MarkupExtension is treated as Constructor argument. The other properties are defined using comma separated strings.
Conclusion
So this article deals with the basics of Markup Extension and Type Converters of basic XAML applications. We have left the Binding markup extension behind, and we will discuss on it on our next topic. I hope this article comes helpful to all of you. Thanks for reading.