As a relative newcomer to Silverlight, I was happily greeted by the warm feeling of familiarity when I started developing. It is surprisingly easy to make the transition from WPF to Silverlight development, with most of the core concepts being just the same. However, there are some parts of the WPF framework that you start to miss. One of those is ElementName
binding.
For those of you not familiar with the concept, I will give a very brief overview. When binding the properties of your visual elements within XAML, the source of this binding will be the object associated with the element's DataContext
. This works just fine for binding your business objects to the UI that exposes their properties. However, with WPF, binding gives you so much more than just a mechanism for exposing your business data, it also allows you to bind properties between visual elements. This is a powerful concept that allows you to create complex layouts, beyond that which is possible by the framework provided Panels (for some examples of this, see my article on creating a Bullet Graph). In order to enable this, WPF provides ElementName
and RelativeSource
bindings, giving you a powerful mechanism for locating other elements within your visual tree to bind to. A simple example where a rectangle’s Width is bound to a named slider is given below:
<Rectangle Width="{Binding Path=Value, ElementName=MySlider}" Height="20" Fill="Green"/>
<Slider x:Name="MySlider" Value="25" Minimum="0" Maximum="300"/>
Unfortunately, Silverlight does not have this capability.
My first thought was to simply point the DataContext
of the target element to the source element in order to allow property binding between them. However, much to my surprise, Silverlight dependency properties do not support property changed notification.
A common solution to this problem is to employ a Relay Object, as described in a number of blog posts. An object with a single property, Value
, which implement INotifyPropertyChanged
is bound to the two visual elements. A simple example is illustrated below:
<UserControl.Resources>
<local:RelayObject x:Key="Relay">
<local:RelayObject.Value>
<system:Double>20</system:Double>
</local:RelayObject.Value>
</local:RelayObject>
</UserControl.Resources>
<StackPanel x:Name="LayoutRoot" Background="White">
<Rectangle Width="{Binding Path=Value, Source={StaticResource Relay},
Mode=TwoWay" Height="20" Fill="Green"/>
<Slider Value="{Binding Path=Value, Source={StaticResource Relay},
Mode=TwoWay}" Minimum="0" Maximum="300"/>
</StackPanel>
Here an instance of our RelayObject
bound to both the Rectangle
’s Width
and the Slider
’s Value
, effectively binding these two properties together. This works just fine, however it does not really feel like the WPF ElementName
binding, furthermore, you have to add a new RelayObject
instance for each binding.
My solution makes use of the Attached Behaviour pattern which is becoming very popular in WPF and Silverlight. First, we define an attached property which uses a type which contains the information we need to create our binding, i.e., source and target properties, and the name of the element which we wish to bind to.
public class BindingProperties
{
public string SourceProperty { get; set; }
public string ElementName { get; set; }
public string TargetProperty { get; set; }
}
public static class BindingHelper
{
public static BindingProperties GetBinding(DependencyObject obj)
{
return (BindingProperties)obj.GetValue(BindingProperty);
}
public static void SetBinding(DependencyObject obj, BindingProperties value)
{
obj.SetValue(BindingProperty, value);
}
public static readonly DependencyProperty BindingProperty =
DependencyProperty.RegisterAttached
("Binding", typeof(BindingProperties), typeof(BindingHelper),
new PropertyMetadata(null, OnBinding));
private static void OnBinding(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement targetElement = depObj as FrameworkElement;
targetElement.Loaded += new RoutedEventHandler(TargetElement_Loaded);
}
private static void TargetElement_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement targetElement = sender as FrameworkElement;
BindingProperties bindingProperties = GetBinding(targetElement);
FrameworkElement sourceElement =
targetElement.FindName(bindingProperties.ElementName) as FrameworkElement;
CreateRelayBinding(targetElement, sourceElement, bindingProperties);
}
}
What the above code does is it defines the attached property of type BindingProperties
. The OnBinding
method is invoked whenever this property is attached to a dependency object. Within this event handler, we add a handler to the element’s Loaded
event. This event occurs when the element is laid out within the visual tree and ready for action, it is at this point that we can perform our ElementName
lookup.
Within the TargetElement_Loaded
event handler, we use the FrameworkElement.FindName to look-up the named source element for our binding. This method locates any element with the given name that is within the same XAML namescope as itself. Interestingly, this is the same method that Visual Studio uses for creating member variables within your code-behind file from the named elements within your XAML. Once the named element has been located, a relay binding is constructed between them, as follows:
private static readonly BindingFlags dpFlags =
BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
private static void CreateRelayBinding
(FrameworkElement targetElement, FrameworkElement sourceElement,
string targetProperty, string sourceProperty, IValueConverter converter)
{
ValueObject relayObject = new ValueObject();
FieldInfo[] sourceFields = sourceElement.GetType().GetFields(dpFlags);
FieldInfo sourceDependencyPropertyField =
sourceFields.First(i => i.Name == sourceProperty + "Property");
DependencyProperty sourceDependencyProperty =
sourceDependencyPropertyField.GetValue(null) as DependencyProperty;
relayObject.Value = sourceElement.GetValue(sourceDependencyProperty);
Binding targetToRelay = new Binding();
targetToRelay.Source = relayObject;
targetToRelay.Path = new PropertyPath("Value");
targetToRelay.Mode = BindingMode.TwoWay;
targetToRelay.Converter = converter;
FieldInfo[] targetFields = targetElement.GetType().GetFields(dpFlags);
FieldInfo targetDependencyPropertyField =
targetFields.First(i => i.Name == targetProperty + "Property");
DependencyProperty targetDependencyProperty =
targetDependencyPropertyField.GetValue(null) as DependencyProperty;
targetElement.SetBinding(targetDependencyProperty, targetToRelay);
Binding sourceToRelay = new Binding();
sourceToRelay.Source = relayObject;
sourceToRelay.Path = new PropertyPath("Value");
sourceToRelay.Mode = BindingMode.TwoWay;
sourceElement.SetBinding(sourceDependencyProperty, sourceToRelay);
}
The above code simply creates our relay object, initialising its value from the source element, then constructs the bindings from relay-to-source and relay-to-target. The only ‘tricky’ part of the above code is the use of reflection to locate the static dependency properties of each element.
Using the above attached property (behaviour), an element name binding can be constructed as follows:
<Rectangle Height="20" Fill="Green">
<local:BindingHelper.Binding>
<local:BindingProperties ElementName="Slider"
SourceProperty="Value" TargetProperty="Width"/>
</local:BindingHelper.Binding>
</Rectangle>
<Slider x:Name="Slider" Value="20" Minimum="0" Maximum="300"/>
The advantages of this approach are twofold, firstly, we do not have to explicitly create a relay object for each ElementName
bindings, secondly, the source property value is used to initialise the relay object directly. It is also a straightforward exercise to extend the above to add ValueConverters
into the binding.
But what if we want to bind two different properties to our slider? If, for example, we want to bind the Widths of two rectangles, we would need to ensure that a single relay object is constructed which is shared by the Slider
and both Rectangles
. A simple solution to this problem is to maintain a dictionary of the source bindings to their associated relay objects. If we bind more than one target property to a particular source property, the relay object is re-used. Any value converters are specified on the target binding, therefore we can bind multiple properties to the source with different converters.
The following example shows a couple of Rectangles
bound to our slider where their Width
s are scaled by different factors:
<Rectangle Height="20" Fill="Green">
<local:BindingHelper.Binding>
<local:BindingProperties ElementName="Slider" SourceProperty="Value"
TargetProperty="Width" Converter="{StaticResource ScalingConverter}"
ConverterParameter="2"/>
</local:BindingHelper.Binding>
</Rectangle>
<Rectangle Height="20" Fill="Green">
<local:BindingHelper.Binding>
<local:BindingProperties ElementName="Slider" SourceProperty="Value"
TargetProperty="Width" Converter="{StaticResource ScalingConverter}"
ConverterParameter="4"/>
</local:BindingHelper.Binding>
</Rectangle>
<Slider x:Name="Slider" Value="20" Minimum="0" Maximum="300"/>
And here is a demonstration of the above implemented both manually with relay objects and with this attached behaviour:
[See element name binding in action on my blog.]
For details of the above, please refer to the attached project download.
This approach does have a few downsides - unfortunately, the syntax is a little verbose. Also, in its present form, you can only create one element name binding per visual element. However, the interesting feature of this technique is that it could be readily adapted to search for the source element in other ways, for example, emulating the WPF relative source bindings. Perhaps I will have a go at that one next week …