Saturday 3 May 2008

Silverlight 2 - Creating bindings between FrameworkElements using Silverstone

I spent an evening last week working on some code for my Silverstone framework which would allow you to bind a property on an element in XAML to a property on another element. This is something which you can do very easily in WPF, using the {Binding ElementName=xxx} markup extension, but is not currently possible in the Beta 1 version of Silverlight 2.

The syntax I wanted to achieve would be something like this:

<Silverstone:ElementNameBinding Target="textBlock" TargetPropertyName="Text" Source="textBox" SourcePropertyName="Text" />
Essentially I would create a separate control which would bind the text of a source TextBox to the text of a target TextBlock called within the same UserControl (although obviously this can be used with any arbitrary property). The idea to demonstrate this is that when the user changes the text of the TextBox, the text of a TextBlock on the page would reflect what he is typing.

I got as far as getting the code working, so the text was initially populated into the target TextBlock. I did this by creating a Binding between the properties using a bit of reflection combined with the existing SetBinding() method. However, the binding was not picking up any changes! This is because the BindingExpression implementation in Siverlight does not expect the binding source to be a DependencyProperty but rather it expects to find an implementation of INotifyPropertyChanged.

So I continued, trying to manually find the DependencyProperty which was being sourced and adding a listener to its change event. However there is no way to do this in Silverlight! Thinking in WPF terms, I was expecting to be able to grab hold of the metadata for the dependency property and add a handler through that, or alternatively to instantiate a DependencyPropertyDescriptor and calling the AddValueChanged() method.

Neither of these exist in Silverlight 2 Beta 1, so I started playing around in Reflector and trying to manually attach to it, something along the lines of:

DependencyProperty boundProperty = (DependencyProperty)
    propertyType.GetField(this.PropertyName + "Property").GetValue(target);

PropertyChangedCallback propertyChangedCallback = (PropertyChangedCallback)
    boundProperty.GetType().GetField("_propertyChangedCallback", 
        BindingFlags.NonPublic | BindingFlags.Instance)
    .GetValue(boundProperty);

Unfortunately, I ran into the good old Security exception - turns out all this code I was trying to access is marked with the SecurityCriticalAttribute which means you can't access it through reflection.

I pretty much gave up at this point. Begrudgingly I closed my laptop, kissed my girlfriend goodnight and drifted to sleep whilst trying to think of an alternative...

Well a week has gone by now and I thought I'd write this blog article to describe what I'd tried, in case anyone else wanted to try the same thing. It was only when describing my problem that I realised I had been leading myself completely in the wrong direction with my attempts. I could easily utilise the existing binding framework to do the job for me!

All I would need to do is create a "surrogate property" on the binder, create a two-way binding from the source to the surrogate property, and another one-way binding from the surrogate property to the target. By implementing the INotifyPropertyChanged interface on the binder control, I could notify the target whenever the source had changed it would automatically re-read the value. It worked a treat and was so simple!

I think the old saying "if at first you dont succeed..." is very appropriate here. I would also consider the relevance of the "go home and sleep on it" phrase. How many times do you work on problem for an entire day but to no avail, then go home and when come back in the next morning you have the problem solved even before your morning cup of tea!

Anyway, the class Silverstone.ElementNameBinder has been updated now supports this. It is available as of version 0.1 of Silverstone.dll. Get it here or get the source code from the svn repository. If anyone decides to use it and has any questions, of course please let me know.

Have a great day!

Neil

2 comments:

Anonymous said...

All I would need to do is create a "surrogate property" on the binder, create a two-way binding from the source to the surrogate >>property, and another one-way binding from the surrogate property to the target. By implementing the INotifyPropertyChanged interface on the binder control, <<<
Have you the snippet of that?
hannesp AT ppedv.de - thanks

Neil Mosafi said...

Sure, I sent you an email with the class in it.