WPF Data Templates Part 2 – Value Converters

Please follow and share!
Twitter
Facebook
LinkedIn
RSS

Sometimes the value you want to display needs to be transformed from the original data before being bound to a XAML element. For instance, formatting a telephone number, or adding brackets to a negative balance on an account.  To accomplish this task a Value Converter is used.  A Value Converter is simply a class that implements the IValueConverter interface (located in the System.Windows.Data namespace).  This interface contains two methods, Convert, and ConvertBack.  It is not necessary to implement the ConvertBack method unless you are persisting data back to a data source and need the reverse transformation from the visual back to the persisted value.  It is important to remember that Value Converters can take in any value and return any type of object, for instance, an integer can be transformed into a string, and a string can be turned into a user control.   In the example provided below, an integer value is converted to a User Control that has the ability to display a 5 star rating.  Here is the XAML of the FiveStarRating user control which is made up of 5 paths in the shape of a star:

<UserControl x:Class="ValueConverter.FiveStarRating"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<StackPanel Orientation="Horizontal">
<Path x:Name="Star1" Margin="0,15,5,0" Stroke="Black" StrokeThickness="2" StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />
<Path x:Name="Star2" Margin="0,15,5,0" Stroke="Black" StrokeThickness="2" StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />
<Path x:Name="Star3" Margin="0,15,5,0" Stroke="Black" StrokeThickness="2" StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />
<Path x:Name="Star4" Margin="0,15,5,0" Stroke="Black" StrokeThickness="2" StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />
<Path x:Name="Star5" Margin="0,15,5,0" Stroke="Black" StrokeThickness="2" StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />
</StackPanel>
</Canvas>
</UserControl>

Source code for the FiveStarRating user control is as follows, contains the logic to fill the correct number of stars dependent on the Rating property of the instance:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
 
namespace ValueConverter
{
/// <summary>
/// Interaction logic for FiveStarRating.xaml
/// </summary>
public partial class FiveStarRating : UserControl
{
public int Rating
{
get { return (int)this.GetValue(RatingProperty); }
set {
if (value > 5)
value = 5;
if (value < 0)
value = 0;
 
this.SetValue(RatingProperty, value); }
}
 
public static readonly DependencyProperty RatingProperty = DependencyProperty.Register("Rating", 
typeof(int),typeof(FiveStarRating),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(RatingValueChanged)
));
 
public FiveStarRating()
{
InitializeComponent();


}
 
private static void RatingValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
 
FiveStarRating x = sender as FiveStarRating;
int ratingValue = (int)e.NewValue;
 
for (int i = 1; i <= 5; i++)
{
Path clearStar = (Path)x.FindName("Star" + i);
clearStar.Fill = new SolidColorBrush(Colors.Gray);
}
for (int i = 1; i <= ratingValue; i++)
{

Path starFilled = (Path)x.FindName("Star" + i);
starFilled.Fill = new SolidColorBrush(Colors.Goldenrod);

}

}
}

}

The Rating property of the FiveStarRating user control has been defined as a DependencyProperty, so it is possible to bind directly to it, but in this example, we will be using this user control as the result of a value converter transformation instead.  The class definition and data being bound is defined in this previous blog post.  The value converter we will implement will convert the integer ImageRating value and transform it into a bound FiveStarRating user control.  The listing for the value converter is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
 
namespace ValueConverter
{
public class RatingConverter : IValueConverter
{
#region IValueConverter Members
 
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//in order to handle design time problems, handle null value case
if (value == null)
return new FiveStarRating();


FiveStarRating uc = new FiveStarRating();
uc.Rating = (int)value;
 
return uc;

}
 
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
 
#endregion
}
}

In order to utilize this Value Converter during data binding, it must first declare the namespace in the window tag, in this case the namespace is ValueConverter which resides in the current project assembly:

xmlns:local="clr-namespace:ValueConverter"

Then define an instance of the value converter class in Resources:

<local:RatingConverter x:Key="RatingConverter" />

This instance of the RatingConverter can now be used in the data template.  In order to utilize the rating converter, identify the resource (identified by the key in Resources, in this case RatingConverter) and assign it to the converter property of the binding expression.

<Label Content="{Binding Path=ImageRating, Converter={StaticResource RatingConverter}}" />

This binding expression passes in the value stored in the ImageRating property of the object being bound into the Convert method of the RatingConverter value converter.  The value converter then instantiates a new FiveStarRating user control, assigns the integer value to its Rating property, and returns the user control instead of the original integer ImageRating value.  The user control is then rendered in the Label control.

image

Complete source code for this sample is available here.

Please follow and share!
Twitter
Facebook
LinkedIn
RSS

Comments

  1. Thanks, I’ve looking for an StarRating control exactly like this. I made this implementation to change the Rating on click:

    XAML:

    <Path MouseEnter="Star_MouseEnter" MouseDown="Star_MouseDown" MouseLeave="Star_MouseLeave" x:Name="Star1" Margin="0,1,5,0" Stroke="Black" StrokeThickness="2" StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />

    <Path MouseEnter="Star_MouseEnter" MouseDown="Star_MouseDown" MouseLeave="Star_MouseLeave" x:Name="Star2" Margin="0,1,5,0" Stroke="Black" StrokeThickness="2" StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />

    <Path MouseEnter="Star_MouseEnter" MouseDown="Star_MouseDown" MouseLeave="Star_MouseLeave" x:Name="Star3" Margin="0,1,5,0" Stroke="Black" StrokeThickness="2" StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />

    <Path MouseEnter="Star_MouseEnter" MouseDown="Star_MouseDown" MouseLeave="Star_MouseLeave" x:Name="Star4" Margin="0,1,5,0" Stroke="Black" StrokeThickness="2" StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />

    <Path MouseEnter="Star_MouseEnter" MouseDown="Star_MouseDown" MouseLeave="Star_MouseLeave" x:Name="Star5" Margin="0,1,5,0" Stroke="Black" StrokeThickness="2" StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />

    CODE:

    private void Star_MouseEnter(object sender, MouseEventArgs e)

    {

    int ratingValue = 0;

    Path x = sender as Path;

    switch (x.Name)

    {

    case "Star1":

    ratingValue = 1;

    break;

    case "Star2":

    ratingValue = 2;

    break;

    case "Star3":

    ratingValue = 3;

    break;

    case "Star4":

    ratingValue = 4;

    break;

    case "Star5":

    ratingValue = 5;

    break;

    }

    for (int i = 1; i <= 5; i++)

    {

    Path clearStar = (Path)this.FindName("Star" + i);

    clearStar.Fill = new SolidColorBrush(Colors.Gray);

    }

    for (int i = 1; i <= ratingValue; i++)

    {

    Path starFilled = (Path)this.FindName("Star" + i);

    starFilled.Fill = new SolidColorBrush(Colors.Goldenrod);

    }

    }

    private void Star_MouseLeave(object sender, MouseEventArgs e)

    {

    int ratingValue = (int)this.GetValue(RatingProperty);

    for (int i = 1; i <= 5; i++)

    {

    Path clearStar = (Path)this.FindName("Star" + i);

    clearStar.Fill = new SolidColorBrush(Colors.Gray);

    }

    for (int i = 1; i <= ratingValue; i++)

    {

    Path starFi

Comments are closed.