Table of Contents
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.
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.
- Value Type : This determines the Type to which the string data should be converted to.
- 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.
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 :
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
{
public override bool CanConvertFrom(
System.ComponentModel.ITypeDescriptorContext context, Type sourceType)
{
if (sourceType is string)
return true;
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(
System.ComponentModel.ITypeDescriptorContext context, Type destinationType)
{
if (destinationType is string)
return true;
return base.CanConvertTo(context, destinationType);
}
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);
}
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.
- 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.
- 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.
- ConvertFrom :Does actual conversion and returns the
GeoPointItem
after successful conversion.
- 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.
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 :
This is the most simple MarkupExtension
we ever have. It actually returns Null when
placed against a value.
Content = "{x:Null}"
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}
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.
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
.
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}"
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.
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.
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.
-
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 in WPF with Language Converter Tool
[^]
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 Binding
[
^]
In 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 Collection
[^]
We will discuss Binding in greater detail in next article.
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 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 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>
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.
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.