Signum Framework Logo
"Open framework that encourages convention over configuration, using C# code,
not XML files, to model at the right level of abstraction and achieve deadlines.
...but also has a full Linq provider, and syncs the schema for you!"
Login
RSS

Search

»



Main
Index
Map
Videos
Download
Source code
Tutorials
Forum
FAQ



Image



PoweredBy

Converters

RSS
Modified on 2009/03/18 03:35 by helen Categorized as SignumWindows

Introduction

Binding infrastructure provided by WPF is a great step forward compared with the available in WinForms. Thanks to the expressiveness provided by Xaml and, specifically Markup Extensions, now we have flexibility choosing the binding Mode and Source.

When a converter is needed, however, the expressiveness of Markup Extensions reach their limit and all the bloat code appears. In order to bind a color property to the background of a Border element (a Brush) you need to:

1. Write a class that implements IValueConverter (2 methods to implement):

   public class BushConverter : IValueConverter
   {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return new SolidColorBrush((Color)value);
        }

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

2. Probably add the namespace at the top of your Xaml file:

  <Window
   (...)
   xmlns:w="clr-namespace:Bugs.Windows" />


3. Add an instance of the converter to the Windows Resources:

    <Window.Resources>
        <w:BushConverter x:Key="brushConverter"/>
    </Window.Resources>

4. Actually use the converter in your binding using StaticResource:

  <Border .... Background="{Binding SelectedColor, ElementName=cp, Converter={StaticResource brushConverter}}" />


C'mon Redmond, you can do better! What is this? Java? (BTW Stevey, pleeease, it's time to reconsider your opinion on C# 3.0).

ConverterFactory

Our small step forward in this problem is using an small set of general purpose converters and relying on lambdas to do the actual conversion. This way you need to create a new converter object, not a new class, every time you need a converter:

1. Create a new converter object using ConverterFactory and store then in a public static readonly field in your own converter repository class.

    public static class MyConverters
    {
         (...)
         public static readonly IValueConverter ColorBrushConverter = ConverterFactory.New(
            (Color color) => new SolidColorBrush(color)); 
         (...)
    }

2. Actually use the converter in your binding using x:Static:

  <Border .... Background="{Binding SelectedColor, ElementName=cp, Converter={x:Static w:MyConverters.ColorBrushConverter}}" />


Notice how, by explicitly specifying Color as the first parameter in the lambda, we are helping type inference algorithm to succeed. This way we save ourselves from writing the type parameters explicitly: ConverterFactory.New<Color,SolidColorBrush>(...)

Let's take a look at the actual method provided by ConverterFactory:

    public static class ConverterFactory
    {
        //Converter from Source to Target
        public static Converter<S, T> New<S, T>(Func<S, T> convert)

//Converter from 2 Sources to Target public static DualConvertet<S1, S2, T> New<S1, S2, T>(Func<S1, S2, T> convert)

//Converter from Many Sources (of the same type) to a Target public static MultiConverter<S, T> NewMulti<S, T>(Func<S[], T> convert)

//Converter from Source to Target and from Target to Source public static Converter<S, T> New<S, T>(Func<S, T> convert, Func<T, S> convertBack)

//Converter from Source to Target and from Target to Source validating the Source. public static ConverterValidator<S, T> New<S, T>(Func<S, T> convert, Func<T, S> convertBack, Func<S, string> validator) }

public class Converter<S, T> : IValueConverter { internal Func<S, T> convert; internal Func<T, S> convertBack; (...) }

public class DualConvertet<S1, S2, T> : IMultiValueConverter { internal Func<S1, S2, T> convert; (...) }

public class MultiConverter<S, T> : IMultiValueConverter { internal Func<S[], T> convert; (...) }

public class ConverterValidator<S, T> : ValidationRule, IValueConverter { internal Func<S, T> convert; internal Func<T, S> convertBack; internal Func<S, string> validator; (...) }

This technique removes a lot of the bloat code needed to implement the converter, and also centralizes all your converters in the same repository class, making it easier to maintain and reuse them.

There are more breave strategies to improve Converter integration in Xaml. Fikrim Var Netleştirelim comes with the great idea of writing the lambdas straight in Xaml. The implementation looks a bit experimental though, it needs a whole parser to do it, and relies on your writing lambdas in string literal. Not IntelliSense. No type checking.

Piping Converters

Based on Josh Smith Tutorial, we also created the PipeExtension to enable concatenation of Converters.

PipeExtension is a Markup Extension and a Converter at the same time.

  public class PipeExtension : MarkupExtension, IValueConverter
  {
        public IValueConverter First { get; set; }
        public IValueConverter Second { get; set; }
        (...)
  }

  • As a Markup Extension it has two parameters, First and Second, both of types IValueConverter. It also returns itself when ProvideValue is called.
  • As a Converter it uses the First converter to get an intermediate value, and uses the Second converter to get the final one. It also works backwards.

Finally, since it only needs the converters to be IValueConverters, it works on both the new ConverterFactory and the old fashioned ones.

Let's see an example, imagine you already have a converter to make colors darker:

        public static readonly IValueConverter DarkColor = ConverterFactory.New(
               (Color c) => Color.FromArgb(c.A, (byte)(c.R / 2), (byte)(c.G / 2), (byte)(c.B / 2)));

Now you want to use it to bind to the background property of a border, that still is a Bush. Instead of writing a new converter you can 'pipe' the two that you already have in a much more flexible way:

<Border Margin="10" 
Background="{Binding SelectedColor, ElementName=cp, Converter={m:Pipe First={x:Static w:MyConverters.DarkColor}, Second={x:Static w:MyConverters.ColorBrushConverter}}}" 
Width="100" Height="100"/> 
Creative Commons License Signum Framework Site by Signum Software is licensed under a Creative Commons Attribution 3.0 License.
Powered by ScrewTurn Wiki version 3.0.5.600.