Posts Tagged ‘dependency properties’

3
Dec
2010

Watching Silverlight Dependency Properties with Reactive Extensions

by Stuart Harris

At yesterday’s Sliverlight Firestarter, Scott announced that Silverlight 5 will have a DataContextChanged event.  Great news!  How often have you wanted to grab the view model in the code behind and been frustrated by the fact that in Silverlight (unlike WPF) you can’t be easily notified when you have a new view model – you can’t even add your own handler by overriding the DataContext property’s metadata.

So we will wait patiently for Silverlight 5 (and all its other goodness – I was very excited by the 3D demo).  But in the meantime we could use something like Prism’s DependencyPropertyListener or BindingListener.  The former uses the traditional approach of using an attached property (with your own handler) to bind to the dependency property in question.  You have to create a binding, attach and detach it in the right place and hook onto (and off) the listener’s Changed event.  Quite a bit of plumbing.  Prism’s BindingListener sits on top of this and allows you to supply a handler and deal directly with elements and bindings.  All very useful.

But when I want the DataContext, or I just want to know when a dependency property’s value has changed, I really don’t want to dig out a pattern from the recesses of my mind and write a ton of code (which is likely wrong first time) just to be notified when something changes.  It’s gotta be one line of code!  Like this:

this.GetChanges(new Binding()).Subscribe(this.DataContextChanged);

Or if I want to know when the Width of a Canvas changes:

this.GetChanges(new Binding("Width") { Source = this.canvas })    
    .Subscribe(args => Debug.WriteLine(args.NewValue));

Obviously I can create any binding I want and pass it to GetChanges(), which is a simple extension method on FrameworkElement that returns an IObservable of ValueChangedEventArgs.  I can subscribe to this with a handler that will be called each time the binding’s value changes.  The extension method looks like this:

public static IObservable<ValueChangedEventArgs> GetChanges(
    this FrameworkElement element, Binding binding)
{
    return Observable.CreateWithDisposable<ValueChangedEventArgs>(
        observer =>
            {
                var listener = new DependencyPropertyListener();
                return Observable.FromEvent<ValueChangedEventArgs>(
                    handler =>
                        {
                            listener.ValueChanged += handler;
                            listener.Attach(element, binding);
                        }, 
                    handler =>
                        {
                            listener.ValueChanged -= handler;
                            listener.Detach();
                        }).Select(@event => @event.EventArgs).Subscribe(observer);
            });
}

So this is the DependencyPropertyListener that was adopted from Prism:

public class DependencyPropertyListener
{
    private readonly DependencyProperty dependencyProperty;

    private FrameworkElement targetElement;

    public DependencyPropertyListener()
    {
        this.dependencyProperty = DependencyProperty.RegisterAttached(
            Guid.NewGuid().ToString(), 
            typeof(object), 
            typeof(DependencyPropertyListener), 
            new PropertyMetadata(null, this.OnValueChanged));
    }

    public event EventHandler<ValueChangedEventArgs> ValueChanged;

    public void Attach(FrameworkElement target, Binding binding)
    {
        if (target == null)
        {
            throw new ArgumentNullException("target");
        }

        if (binding == null)
        {
            throw new ArgumentNullException("binding");
        }

        this.Detach();
        this.targetElement = target;
        this.targetElement.SetBinding(this.dependencyProperty, binding);
    }

    public void Detach()
    {
        if (this.targetElement != null)
        {
            this.targetElement.ClearValue(this.dependencyProperty);
            this.targetElement = null;
        }
    }

    private void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        EventHandler<ValueChangedEventArgs> handler = this.ValueChanged;
        if (handler != null)
        {
            handler(this, new ValueChangedEventArgs(e.OldValue, e.NewValue));
        }
    }
}

Presumably the System.Windows.DependencyPropertyChangedEventArgs is not derived from EventArgs because it’s a struct for performance reasons.  It’s up to you whether you use that or create your own EventArgs like this:

public class ValueChangedEventArgs : EventArgs
{
    private readonly object newValue;

    private readonly object oldValue;

    public ValueChangedEventArgs(object oldValue, object newValue)
    {
        this.oldValue = oldValue;
        this.newValue = newValue;
    }

    public object NewValue
    {
        get
        {
            return this.newValue;
        }
    }

    public object OldValue
    {
        get
        {
            return this.oldValue;
        }
    }
}

Binding to the Windows Phone 7 Application Bar

The Application Bar in Windows Phone 7 (and its buttons and menu items) are not derived from DependencyObject and so can’t easily participate in data binding.  Using Silverlight 3 behaviours, we can attach functionality to them, however, including bindings for ICommands (with parameters), Text and Icons.

<phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar IsVisible="True"
                          IsMenuEnabled="True">
        <shell:ApplicationBarIconButton IconUri="/Images/appbar.add.rest.png"
                                        Text="Add" />
        <shell:ApplicationBarIconButton IconUri="/Images/appbar.delete.rest.png"
                                        Text="Delete" />
        <shell:ApplicationBar.MenuItems>
            <shell:ApplicationBarMenuItem Text="MenuItem 1" />
            <shell:ApplicationBarMenuItem Text="MenuItem 2" />
        </shell:ApplicationBar.MenuItems>
    </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

<i:Interaction.Behaviors>
    <Behaviors:ApplicationBarIconButtonCommand TextKey="Add"
                                               CommandBinding="{Binding Path=AddCommand}"
                                               TextBinding="{Binding Source={StaticResource LocalizedStrings}, Path=LocalizedResources.Add}"
                                               IconBinding="{Binding Path=AddIcon}" />
    <Behaviors:ApplicationBarIconButtonCommand TextKey="Delete"
                                               CommandBinding="{Binding Path=DeleteCommand}"
                                               TextBinding="{Binding Source={StaticResource LocalizedStrings}, Path=LocalizedResources.Delete}" />
</i:Interaction.Behaviors>

I’ve attached a sample project that demonstrates binding to the Application Bar buttons.  There are behaviours for binding Prism’s DelegateCommand to the app bar buttons (and menu items) and to regular buttons and other FrameworkElements.  I haven’t included the full Prism source (just the bits needed to run the sample).  Also there is no IoC container so the MainViewModel is instantiated in App.xaml for this.

Enjoy!

Source Code (Visual Studio 2010 sample solution, built against Build 2787 of Reactive Extensions for WP7):

AppBarBinding.zip (44.42 kb)