Performing Data Conversion with Value Converter Class in Silverlight


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:

  1. Convert data into string representation: converting a number into a string.
  2. 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.
  3. 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.

binding expression in silverlight

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;

  1. 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.

  2. Then we need to implement a Convert() method that changes data from its original format to its display format.

  3. 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,

priceconverterisilverlight.gif

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

lislbox in silverlight

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.

Data Conversion in silverlight

For items whose unit price is more than 50, the background changes

background properties in silverlight

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.