Introduction: In most cases data travels from the source to the target without any change, but this is not the case we
are interested in. Our data source could be some low level information which
we want to encapsulate from showing directly on the user interface. For example, we
might have some numeric codes which we want to show as more human readable
strings. So to achieved this, we need to so some conversion. Also, if we are doing two way binding, we need to handle the converse, which is
taking user input data and converting it to a representation suitable for
storage in the appropriate data object.
Value Converter Class: This Silverlight class is responsible for
converting source data just before it is displayed in the target and also doing the job of converting the new target value just before it is supplied back to the
source.
When we can use a Value Converter Class:
- Convert data into string representation:
converting a number into a string.
- Creates specific type of Silverlight
object: we can read a block of binary data and create a image object that
can be later bound to an image element.
- To conditionally change an element's
property based on a bound control: suppose we want to change the background
color of an element depending on some range, we can make use of value
converter class here.
Okay, so let us take the above three scenarios
one by one and explore what a value converter has to offer us.
Before we start we will create a Silverlight project and we will create a GUI
for displaying the data from an XML data source.
Code for accessing WCF service is below;
private
ObservableCollection<Product>
products = new
ObservableCollection<Product>();
private void
cmdGetProducts_Click(object sender,
RoutedEventArgs e)
{
EndpointAddress address =
new EndpointAddress("http://localhost:"
+
HtmlPage.Document.DocumentUri.Port
+ "/DataBinding.Web/StoreDb.svc");
StoreDbClient client =
new StoreDbClient(new
BasicHttpBinding(), address);
client.GetProductsCompleted += client_GetProductsCompleted;
client.GetProductsAsync();
}
private void
client_GetProductsCompleted(object sender,
GetProductsCompletedEventArgs e)
{
try
{
products.Clear();
foreach (Product
product in e.Result) products.Add(product);
lstProducts.ItemsSource = products;
}
catch (Exception
err)
{
lblError.Text = "Failed to contact
service.";
}
}
Here I have created an XML data source and we are accessing it via a WCF service as a collection of objects and binding it into a listbox. Later on item selection we are
further binding items into textboxes. Look into the XAML you will find that I have
written a binding expression of each.
See the WCF code in the project itself. Here we will concentrate more on the value converter.
Now since we have a WCF service doing the job for us and we are getting data to
display in our GUI, now its time to do some actual data conversions. So we will look at each scenario one by one.
Scenario 1: Formatting a String.
Value converters are the perfect tool for formatting numbers that need to be
displayed as text. For example, consider the Product.UnitCost property. It's
stored as a decimal; and, as a result, when it's displayed in a text box, you
see a value with more decimal places. Not only does this display format show more
decimal places than you'd probably like, but it also leaves out the currency
symbol. A more intuitive representation is the currency-formatted value $2.99
So we will create a value converter class to this formatting.
Steps involved in creating value converter class;
-
We need to create a class that implements
IValueConverter (from the System.Windows.Data namespace). We place this
class in our Silverlight project, which is where the conversion takes
place, and not in the web service.
-
Then we need to implement a Convert() method
that changes data from its original format to its display format.
-
At last we need to implement a ConvertBack()
method that does the reverse and changes a value from the display format to its
native format.
So now add the new class and name it
PriceConverter.cs in our Silverlight project, not in the web service one. Also add the System.Window.Data assembly.
Open PriceConverter.cs, and write below codes.
public
class PriceConverter
: IValueConverter
{
#region
IValueConverter Members
public object
IValueConverter.Convert(object
value, Type targetType,
object parameter, System.Globalization.CultureInfo
culture)
{
double price = (double)value;
return price.ToString("C",
culture);
}
Here we are converting a string into currency. This code uses the culture settings
that apply to the current thread. A computer that's configured for another
locale may display a different currency symbol. If this isn't the result we want
(for example, we always want the dollar sign to appear), you can specify a
culture using the overload of the ToString() method
return
price.ToString("C", culture);
Below is the code in reverse; convert user input data into data source format.
public object
IValueConverter.ConvertBack(object
value, Type targetType,
object parameter, System.Globalization.CultureInfo
culture)
{
string price = value.ToString();
double result;
if(Double.TryParse(price,NumberStyles.Any,culture,out
result))
{
return result;
}
return value;
}
#endregion
Now this is going to be tricky and not so
straight forward. This is because Parse() or TryParse() cannot handle currency
conversion. The solution is to use an overloaded version of the Parse() or
TryParse() method that accepts a System.Globalization.NumberStyles value. If you
supply NumberStyles.Any, you can successfully strip out the currency symbol, if
it exists.
Now to put this converter into action, you begin by mapping your project
namespace to an XML namespace prefix you can use in your markup. Here's an
example that uses the namespace prefix converter and assumes your value
converter is in the namespace DataBinding:
So open main.xmal and add below XAML code.
xmlns:ourconverter="clr-namespace:DataConversion"
Now we will add
this attribute to the <UserControl> start tag at the top of your markup.
<UserControl.Resources>
<ourconverter:PriceConverter
x:Key="PriceConvert">
</ourconverter:PriceConverter>
</UserControl.Resources>
Now we can point
to it in your binding using a StaticResource reference:
<TextBox
Margin="5"
Grid.Row="2"
Grid.Column="1"
Text="{Binding
UnitCost,
Mode=TwoWay,Converter={StaticResource
PriceConverter}}"></TextBox>
So when an item from the listbox is selected, the unit cost is bound to above textbox, and
it calls the Convert method from our value converter class .i.e. price converter
class.
Run and see now we have dollar sign,
You can see now the unit cost is showing up with dollar sign.
Scenario Two: How to create an object with the help of a Value Converter.
Suppose we have an scenario where we have stored picture data as a byte array in a field in a database. We can convert the binary data into a BitmapImage object and can store that as a part of the project. But this approach is not
flexible because we may need to create more than one object representation of
the image in a scenario where our data library is used by Silverlight and WPF as
well.
In this case, it makes sense to store the raw binary data in your data object
and convert it to a BitmapImage object using a value converter.
Open product.cs and check for ProductImagePath property
private
string productImagePath;
[DataMember()]
public string
ProductImagePath
{
get { return
productImagePath; }
set { productImagePath =
value; }
}
The ProductImage field includes the file name but not the full URI of an image
file. This gives us the flexibility to pull the image files from any location.
The value converter has the task of creating a URI that points to the image file
based on the ProductImage field and any website we want to use. The root URI is
stored using a custom property named RootUri, which defaults to the same URI
where the current web page is located.
Now its time to create a class for the ImagePathConverter which will implement the Value
Converter class.
Add a new class and name it ImagePathConverter.cs
and write the below code.
public
class
ImagePathConverter:IValueConverter
{
private string
rootUri;
public string
RootUri
{
get {
return rootUri; }
set { rootUri =
value; }
}
public ImagePathConverter()
{
string uri =
HtmlPage.Document.DocumentUri.ToString();
rootUri = uri.Remove(uri.LastIndexOf('/'),
uri.Length - uri.LastIndexOf('/'));
}
#region
IValueConverter Members
public object
Convert(object value,
Type targetType,
object parameter, System.Globalization.CultureInfo
culture)
{
string imagePath = RootUri +
"/" + (string)value;
// (The database expect GIF files, but
Silverlight only supports PNG and JPEG.)
imagePath = imagePath.ToLower().Replace(".gif",
".png");
return new
BitmapImage(new
Uri(imagePath));
}
public object
ConvertBack(object value,
Type targetType,
object parameter, System.Globalization.CultureInfo
culture)
{
throw new
NotImplementedException();
}
#endregion
}
To use this converter, begin by adding it to Resources.
<UserControl.Resources>
<ourconverter:PriceConverter
x:Key="PriceConverter"></ourconverter:PriceConverter>
<ourconverter:ImagePathConverter
x:Key="ImageConverter"
></ourconverter:ImagePathConverter>
</UserControl.Resources>
And now we need to write the source for image
<Image
Margin="5,7"
Grid.Row="3"
Grid.Column="1"
Stretch="None"
HorizontalAlignment="Left"
Source="{Binding
Path=ProductImagePath,Converter={StaticResource
ImagePathConverter}}">
</Image>
We are ready now , so Prss F5 , get all products and click on any listbox items
You can see, we are able to get image.Cools , isin't
Scenario Three: Conditional formatting
The potential of value converters is not just to format data for representation, but
they can also be used to format other appearance related aspects of an element
based on data rules.
Now the scenario is that we want to highlight high priced items with a different
backgroud.
So let us add another class and name it PriceToBackgroundConverter.cs
Write the below code inside it;
public
class
PriceToBackgroundConverter:IValueConverter
{
public double
MinimumPriceToHighlight
{
get;
set;
}
public Brush
HighlighBrush
{
get;
set;
}
public Brush
DefaultBrush
{
get;
set;
}
#region
IValueConverter Members
public object
Convert(object value,
Type targetType,
object parameter, System.Globalization.CultureInfo
culture)
{
double price = (double)value;
if (price >= MinimumPriceToHighlight)
{
return HighlighBrush;
}
else
{
return DefaultBrush;
}
}
public object
ConvertBack(object value,
Type targetType,
object parameter, System.Globalization.CultureInfo
culture)
{
throw new
NotImplementedException();
}
#endregion
}
You notice that we have used brushes instead of colors so that we can create
more advanced highlight effects using gradients and background images. If we
want to keep the standard Transparent background (so the background of the
parent elements is used), set the DefaultBrush or HighlightBrush property to
null.
Once again, the value converter is carefully designed with reusability in mind.
Rather than hard-coding the color highlights in the converter, they're specified
in the XAML by the code that uses the converter:
<ourconverter:PriceToBackgroundConverter
x:Key="PriceColorConverter"
DefaultBrush="{x:Null}"
HighlighBrush="Orange"
MinimumPriceToHighlight="50"
></ourconverter:PriceToBackgroundConverter>
So now all we need to do is use this converter to set the
background of an element, such as the
Border that contains all the other elements:
<Border
Grid.Row="2"
Padding="7"
Margin="7"
x:Name="borderProductDetails"
Background="{Binding
UnitCost,Converter={
StaticResource
PriceColorConverter}}"
>
In many scenarios, we'll need to pass information to a converter
beyond the data we want to convert. In this example,
PriceColorConverter
needs to know the highlight color and minimum price details, and this information is passed
along through properties.
However, we can pass a single object (of any type) to a converter
through the binding expression, by setting the ConverterParameter property.
Here's an example that uses this approach to supply the minimum price:
<Border
Grid.Row="2"
Padding="7"
Margin="7"
x:Name="borderProductDetails"
Background="{Binding
UnitCost,Converter={
StaticResource PriceColorConverter},ConverterParameter=50}"
>
The parameter is passed as an argument to the Convert() method.
Here's how you can
rewrite the earlier example to use it:
public
object Convert(object
value, Type targetType,
object parameter, System.Globalization.CultureInfo
culture)
{
double price = (double)value;
if (price >=
Double.Parse(parameter))
{
return HighlighBrush;
}
else
{
return DefaultBrush;
}
}
But I personally prefer the property-based approach. It's clearer, more
flexible, and
Strongly typed.
So press F5 and see the result.
For items with a unit cost less than 50, the border background won't change.
For items whose unit price is more than 50, the background changes
Cool, Isin't it?
By this we come to an end of our lesson on data conversion using the Value Converter
class and three different scenarios.
Thanks for reading
Cheers.