Saturday, 11 May 2013

Type Converter Introduction | WPF Tutorial pdf

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 theToString() 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.

No comments: