#1,140 – Using a Value Converter in a Template

You can use a value converter anywhere in XAML where you are using data binding.

Below is an example of using a value converter within a data template.  The Visibility property is bound to the underlying Actor object that is the data context for the item template.  The value converter then derives a value for Visibility from several properties within the Actor object.  (Assume that we have an ActorList property that is a collection of Actor instances).

The XAML includes:

    <Window.Resources>
        <loc:DeadFredConverter x:Key="deadFredConverter"/>
    </Window.Resources>

    <StackPanel>
        <ListBox Margin="15" Width="270" Height="320"
             ItemsSource="{Binding ActorList}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Image Source="{Binding Image}" Height="80"/>
                        <StackPanel Margin="5">
                            <TextBlock Text="{Binding FullName}" FontSize="12" FontWeight="Bold"/>
                            <TextBlock Text="{Binding Dates}"/>
                            <TextBlock Text="{Binding KnownFor}" Margin="0,5,0,0" FontStyle="Italic"/>
                        </StackPanel>
                        <Label Content="Dead Fred !" Foreground="Red"
                               FontWeight="Bold"
                               Visibility="{Binding Converter={StaticResource deadFredConverter}}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>

The body of the value converter is:

    class DeadFredConverter : IValueConverter
    {
        // Convert to Visibility, deriving from properties on Actor object
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            Actor a = (Actor)value;

            Visibility vis = Visibility.Hidden;

            if ((a.FirstName == "Fred") &&
                a.DeathYear.HasValue &&
                (a.DeathYear <= DateTime.Today.Year))
                vis = Visibility.Visible;

            return vis;
        }

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

1140-001

#1,139 – Value Converter Example for Input

The ConvertBack method of a value converter is used to convert from a binding target (e.g. the attribute of a control) to a binding source (e.g. a property).  Below is a simple example, showing how we can convert from a Slider value to the square root of the selected value.

In XAML, we have a Slider that ranges from 1-100 and binds to a property that is meant to store the square root of the selected value.  We specify a binding Mode to indicate that binding should only map from the target (the Value property) to the source (the SqRootValue property) and not in the other direction.  We then include labels that bind to the Slider’s Value property as well as the SqRootValue property.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="Value Converter" SizeToContent="WidthAndHeight">

    <Window.Resources>
        <local:IntToRootConverter x:Key="intToRootConverter"/>
    </Window.Resources>

    <StackPanel Margin="15">
        <Slider x:Name="slider" Minimum="1" Maximum="100"
                IsSnapToTickEnabled="True"
                Value="{Binding Path=SqRootValue,
                                Converter={StaticResource intToRootConverter},
                                Mode=OneWayToSource}"/>
        <Label Content="{Binding ElementName=slider, Path=Value}"/>
        <Label Content="{Binding SqRootValue}"/>
    </StackPanel>
</Window>

The code-behind is straightforward.

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            this.DataContext = this;
            InitializeComponent();
        }

        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        protected virtual void OnPropertyChanged(string prop)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }

        private double sqRootValue;
        public double SqRootValue
        {
            get { return sqRootValue; }
            set
            {
                if (sqRootValue != value)
                {
                    sqRootValue = value;
                    OnPropertyChanged("SqRootValue");
                }
            }
        }
    }

In the value converter, we just take the square root of the current value, calculating the result in ConvertBack.

    public class IntToRootConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        // Convert from int (target of binding) to double representing square root (source of binding)
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            double root = Math.Sqrt((double)value);
            return root;
        }
    }

At run-time:

1139-001

#1,138 – Value Converter Example for Output

The Convert method of a value converter is used to convert from a binding source (e.g. a property) to a binding target (e.g. the attribute of a control).  Below is a simple example, showing how we can convert from an integer to a brush of a particular color.

In XAML, we have a Slider that ranges from 0-255 and binds to a property, storing the integer value that the user selects.  We then bind to the same property for a rectangle shape’s Fill property.  The Fill property wants a Brush object, so we use a value converter to convert from the integer to a brush.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="Value Converter" SizeToContent="WidthAndHeight">

    <Window.Resources>
        <local:IntToBlueBrushValueConverter x:Key="intToBlueBrushValueConverter"/>
    </Window.Resources>

    <StackPanel Margin="15">
        <Slider Minimum="0" Maximum="255"
                Value="{Binding BlueValue}"/>
        <Label Content="{Binding BlueValue}"/>
        <Rectangle Height="80" Width="80"
            Fill="{Binding Path=BlueValue, Converter={StaticResource intToBlueBrushValueConverter}}"/>
    </StackPanel>
</Window>

The code-behind is straightforward.

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            this.DataContext = this;
            InitializeComponent();
        }

        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        protected virtual void OnPropertyChanged(string prop)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }

        private int blueValue;
        public int BlueValue
        {
            get { return blueValue; }
            set
            {
                if (blueValue != value)
                {
                    blueValue = value;
                    OnPropertyChanged("BlueValue");
                }
            }
        }
    }

In the value converter, we create a solid color brush whose R and G values are 0 and whose B value is derived from the integer (slider position).

    public class IntToBlueBrushValueConverter : IValueConverter
    {
        // Convert from int to System.Windows.Media.Brush
        // where brush is color with R=0, G=0 and B=int
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {

            int blueVal = (int)value;
            blueVal = Math.Min(Math.Max(0, blueVal), 255);

            return new SolidColorBrush(Color.FromRgb(0, 0, (byte)blueVal));
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return null;
        }
    }

At run-time, the color of the rectangle changes as we move the slider.

1138-001

1138-002

1138-003

#1,137 – Value Converter Basics

A value converter is a class that knows how to convert data as part of the data binding process.  It can convert data in two directions:

  • From the data type of a bound property to the data type required by the attribute that is binding to the property (output)
  • From the data type of an attribute to the data type of a bound property (input)

A value converter implements the IValueConverter interface.  IValueConverter.Convert is used for output, converting from a bound property value to the type required by an attribute.  For example, you might have a integer property that you bind to and use in setting the background color of a particular control.

IValueConverter.ConvertBack is used for input, converting from an attribute’s value to a bound property.

#1,136 – WPF Controls Are Lookless

In WPF, existing controls are “lookless”, meaning that the control’s behavior (the code) is independent from its appearance (as defined in its control template).

When a control is lookless, you can completely re-define the control’s appearance by providing your own control template for the control.  In general, you can do this without impacting the control’s behavior.

If you don’t specify a control template for the control, you’ll get a default control template that presents the control with a default appearance.

 

#1,135 – Alternatives to Creating a Custom Control

You’ll typically use existing WPF controls when creating an application’s user interface.  You can set property values for the control to customize both the look and behavior of the control.

If an existing control doesn’t provide the desired behavior or visual appearance, you can build a custom control.

Creating a custom control involves a fair amount of work.  Before deciding to create a new control, you should consider the alternatives listed below.

  • Use an existing control
    • Modify behavior or appearance using properties
    • Execute code in response to the control’s events
  • Use a control template to change the appearance of an existing control
  • Use a data template to change how bound data will be rendered by the control
  • Use triggers to change something in the control based on an event or changing data
  • Use value converter to customize data provided to the control
  • Use attached property to provide custom behavior

#1,134 – Localization XV – Localizing Other Content

You’ll sometimes have content in an application that needs to be localized but is not already present in a XAML file.  Below, a MessageBox uses a hard-coded string.

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            string message = "Apples are crunchy, portable and taste great!";
            MessageBox.Show(message, "Apples");
        }

One approach to this is to move the string into a XAML file as a resource and then localize the string when localizing all of the other content in the XAML file.

We start by including an XML namespace that defines an alias for the System namespace.

        xmlns:sys="clr-namespace:System;assembly=mscorlib"

We can now add the string as a resource (e.g. within a <Window> element).

    <Window.Resources>
        <sys:String x:Uid="sys:String_1" x:Key="AppleMessage">Apples are crunchy, portable and taste great!</sys:String>
    </Window.Resources>

At run-time, we can load the resource rather than a hard-coded string.

            string message = (string)this.Resources["AppleMessage"];
            MessageBox.Show(message, "Apples");