MultiBinding for Silverlight 3
One of the key binding features missing from Silverlight 3 is MultiBinding and IMultiValueConverter support. In this post I'll walk through a custom implementation that uses a few tricks that you may want to leverage elsewhere.
First of all, I wanted to re-create the IMultiValueConverter from WPF in it's entirety. Here it is:
public interface IMultiValueConverter
{
object Convert(object[] values, Type targetType, object parameter, CultureInfo culture);
object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture);
}
That was easy. Now for the actual MultiBinding. In WPF a MultiBinding looks like this:
<TextBlock Name="textBox2" DataContext="{StaticResource NameListData}">
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource myNameConverter}"
ConverterParameter="FormatLastFirst">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Ideally I'd have liked to recreate this for Silverlight but without support for custom MarkupExtensions that's just not happening. Instead, we'll have to use another trick where we implement a
non-visual FrameworkElement. We use a FrameworkElement because we want DependencyProperties that support Bindings and these have to belong to FrameworkElement in Silverlight (a DependencyObject isn't good enough in SL3 as it is in WPF). No matter.
The other trick is that we have to add this element to the Visual Tree. What? Yes, I know, it feels like a hack but really it isn't. Even the RIA team do it so it must be OK. The key thing is that our element is non-visual so it doesn't contribute to the Render pipeline (has 0x0 size etc...). This is what it might look like in use:
<binding:MultiBinding x:Name="mb" Converter="{StaticResource intsToBrushConverter}"
NumberOfInputs="3"
Input1="{Binding ElementName=red, Path=Value, Mode=TwoWay}"
Input2="{Binding ElementName=green, Path=Value, Mode=TwoWay}"
Input3="{Binding ElementName=blue, Path=Value, Mode=TwoWay}" />
<Border Background="{Binding ElementName=mb, Path=Output}" Margin="5"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Grid.Row="1">
<Slider x:Name="red" Minimum="0" Maximum="255" Margin="5" Orientation="Vertical"/>
<Slider x:Name="green" Minimum="0" Maximum="255" Margin="5" Orientation="Vertical" />
<Slider x:Name="blue" Minimum="0" Maximum="255" Margin="5" Orientation="Vertical" />
</StackPanel>
You might have guessed that I'm creating a Color Selector control. Wanna' see it in action? Go on then (
can't see it? click here).
Here's the BrushConverter (IMultiConverter) that's used by the MultiBinding:
public class IntsToBrushConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
byte r = System.Convert.ToByte(values[0]);
byte g = System.Convert.ToByte(values[1]);
byte b = System.Convert.ToByte(values[2]);
return new SolidColorBrush(Color.FromArgb(Byte.MaxValue, r, g, b));
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture
{
SolidColorBrush brush = (SolidColorBrush)value;
return new object[] { brush.Color.R, brush.Color.G, brush.Color.B };
}
}
Easy peasy. Want the code? Go on then:
- Usual disclaimers apply - this is demoware and used very much at your own risk.
- Download Source (12 KB)
- Please leave some feedback in the comments if you like it.
Important Notes
You may have noticed a few strange features about our MultiBinding, e.g. how the inputs are specified on a number of Input1, Input2 and Input3 properties instead of on child controls. That's just the way it is I'm afraid (because Bindings only work on members of the visual tree, and since this is a non-visual element any child elements aren't part of the visual tree), and because of this you have to specify the NumberOfInputs you want - my version supports up to 5.
<binding:MultiBinding x:Name="mb" Converter="{StaticResource intsToBrushConverter}"
NumberOfInputs="3"
Input1="{Binding ElementName=red, Path=Value, Mode=TwoWay}"
Input2="{Binding ElementName=green, Path=Value, Mode=TwoWay}"
Input3="{Binding ElementName=blue, Path=Value, Mode=TwoWay}" />
You really can drop this control pretty much anywhere in your visual tree but it makes most sense to keep it close to the parts that need it. As always, your own mileage may vary. Any feedback appreciated in the comments.