Introduction
Multi-binding is a very important/powerful feature in WPF. It comes in handy when you want to get a single result that depends on multiple inputs.
The result is bound to a special logic often implemented inside a 'MultiValueConverter'. So any change to any of the inputs should be manifested in the output result.
In Silverlight, this feature is missing!
In this article, I'll show three solutions I came up with, from 'Quick & Dirty' to the 'WPF-look-alike'.
Background
While working on porting a WPF app to Silverlight, I encountered this 'missing feature' issue (which Silverlight programmers are all familiar with...)
as Silverlight is only a subset of WPF. Like any other software developer, the first thing I did was to Google it...
After examining the suggested approaches, I came out with these conclusions:
- Some of the solutions where too smart/complex for my 'taste'.
- The solutions weren't as robust as I hoped.
For example:
- Lack of support for
DependencyObject
multi-binding (only FrameworkElement
)
- Lack of
RelativeSource
in Binding
.
- The implementation was too far away from the base-line WPF way (in some cases awkward) which makes porting to Silverlight too messy.
So, I thought I'll give it a try and solve it my own way...
Some 'hints' led me to the solutions I came up with:
- MS kept excluding this feature throughout all Silverlight versions, which could have been due to two reasons:
- MS is indifferent to its developers' needs...
- MS 'thinks' that this feature is relatively simple to achieve using the standard available tools.
I found it was more fruitful for me to take the approach that for any algorithmic problem I encounter, there is a simple, elegant, and quick solution, I just need
to find it... so I considered the second reason as the 'real' one.
- Thinking of the core functionality of multibinding should 'ring' one's bell:
Which standard Silverlight feature has the 'ability' to monitor and respond to property changes? Yes, DependencyProperty
! and it's variant AttachedProperty
.
- New features introduced in Silverlight 5.0:
RelativeSource
with AncestorType
MarkupExtension
(in short -'auto' instantiated object (in XAML) that has a return value).
Having these 'hints' in mind, I came out with three solutions:
1. Quick & Dirty
If you have only the multibinding functionality in mind, you can get along with this approach:
- Construct a 'specialized' class with AttachedProperties matching your input bindings (you can even give these properties meaningful names).
- Relay any changes of any of these AttachedProperties to a single 'Any...Changed' subroutine on which you will do two things:
- Perform your desired multibinding logic.
- Update the 'outcome property'.
2. Partial-Elegant
As best practice it is better to seperate multibinding to seperate tasks:
- Multi-input-bindings generic 'engine' (for XAML)
- Multi-values-into-one logic (usually as an implementation of
IMultiValueConverter
).
Having that in mind, we can create an 'AttachedProperty
' based class (called MultiBindingUtil
) that will have any number (for simplicity's sake, the sample
has only three pre-defined ones) of Attached Properties for the input bindings (called Binding1
to Binding
(n)), AttachedProperty
for the Converter
, and finally, an AttachedProperty
for the result (called MBResult
in the sample). Now all we have to do in XAML
is to bind the input binding AttachedProperties to whatever we like, supply a converter, and bind the MBResult
back to the property we would like to be changed by the multibinding.
Inside the multibinding 'engine', just like the previous example, we'll relay any input-binding changes into a single 'Any...Changed' subroutine;
only, there, we'll update the MBResult
property with the converted result of the supplied converter, using the class's bindings as the input.
All is well, except there is a well documented/reported design-time bug occurring in Silverlight when binding an element's property to its own
attached property. This bug manifests itself in a very subtle and informative way:
I couldn't find any solution for this 'local-path' issue. If any of you know how to fix it, please share.
3. WPF-Alike
This solution gives a very close XAML look as WPF's multibinding, which in cases of porting might be helpful. Also, it utilizes the new Silverlight 5.0 MarkupExtension
support and the previous solution's MultiBindingUtil
class. Here we are using a new class of type MarkupExtension
called 'MyMultiBinding(Extension)
',
which has a Content
property List<Binding>
called (oddly) 'Bindings
' and a Converter
property
of type IMultiValueConverter
called (oddly) 'Converter
'.
Setting this property as content is done using this attribute:
[ContentProperty("Bindings")]
The actual trick is done inside the MarkupExtension
's most significant override method:
override object ProvideValue(IServiceProvider serviceProvider)
We'll do the following actions:
- Get the 'target'
FrameworkElement
:
IProvideValueTarget pvt =
serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
DependencyObject TargetObject = pvt.TargetObject as DependencyObject ;
- 'Attach' to it
MultiBindingUtil
's bindings properties with matching values of 'our' Bindings
list.
for (int i = 0; i < _Bindings.Count ; i++)
{
string sDpName="Binding"+(i+1).ToString()+"Property";
FieldInfo fi = typeof(MultiBindingUtil).GetField(sDpName);
if (fi==null)
{
throw new IndexOutOfRangeException(
"MultiBindingUtil Max number of binding(3) exceeded");
}
DependencyProperty dp = (DependencyProperty)fi.GetValue(null);
BindingExpressionBase beb =
BindingOperations.SetBinding(TargetObject, dp, _Bindings[i]);
}
- 'Attach' to it
MultiBindingUtil
's Converter
property with value of 'our' Converter
.
TargetObject.SetValue(MultiBindingUtil.ConverterProperty, this.Converter);
- Set the return value to
Binding
(rather than a simple value). This binding will bind to MBResult MultiBindingUtil
's attached property(!).
Binding b = new Binding();
b.Source = TargetObject;
b.Path =new PropertyPath(MultiBindingUtil.MBResultProperty);
return b;
Remarks
- Solutions 1, 2 should be compatible with Silverlight 4.0.
- Solution 3 doesn't work for Style Setter multi-binding.
The Quick & Dirty solution (#1) can help there, like that:
<Setter Property="mb:MultiBindingReplacement.EpisodeLength"
Value="{Binding Path=RangeMilSec}" />
<Setter Property="mb:MultiBindingReplacement.Scale"
Value="{Binding Path=VM.ScaleTransScaleX}" />
Where MultiBindingReplacement
is the name of the special class, and EpisodeLength
and Scale
are special
DependencyProperty
s for the bindings.
Inside the class, I perform the 'conversion logic' and set the result to a special property in the Style's targeted object (as explained in solution 3).