Sunday 26 July 2009

Reacting to the Reactive Framework: Part 5

It appears to have gone mostly under the radar, but the Reactive Framework is now out in the wild. The latest release of the Silverlight Toolkit includes System.Reactive.dll, mostly to facilitate the testing of the controls found within the toolkit. Jafar Husain broke the news earlier this week with this excellent post.

Today I want to do a demo that is very similar to the previous demos I’ve done, but of course this time I’ll be using the real Reactive Framework rather than my crappy attempt at implementing it that I’ve inflicted upon you in earlier posts. So far my experiments with using System.Reactive.dll with windows forms have not compiled properly so I’ll switch to using Silverlight for my examples. For now I will continue to focus on exposing button click events as an IObservable. Here is the code:

IObservable<Event<RoutedEventArgs>> clicks = Observable.FromEvent<RoutedEventArgs>(button1, "Click");

int count = 0;
clicks.Subscribe(() => count++);

IObservable<string> messages = from c in clicks
                               select string.Format("Clicked {0} time{1}", count, count > 1 ? "s" : "");
messages.Subscribe(s => button1.Content = s);

And here is the application:

If you are in a RSS reader, you probably won’t be able to see the embedded Silverlight app above. Pop this post out into its own browser tab so that you can revel in the glory of a button that tells you how many times its been clicked!

Lets take a look at what this code is doing. The first step is to convert from an Event to an IObservable:

IObservable<Event<RoutedEventArgs>> clicks = Observable.FromEvent<RoutedEventArgs>(button1, "Click");

The Observable class defines a large swath of extension methods for creating and manipulating IObservables. This overload of the FromEvent method is simple to use, but unfortunately it makes use of a magic string (“click”). There is another overload for FromEvent that allows us to do the same thing with compile-time safety, but it is more verbose. I’ve wrapped it in an extension method:

public static IObservable<Event<RoutedEventArgs>> GetClicks(this Button button)
{
    return Observable.FromEvent((EventHandler<RoutedEventArgs> genericHandler) => new RoutedEventHandler(genericHandler),
                                routedHandler => button.Click += routedHandler,
                                routedHandler => button.Click -= routedHandler);
}

This overload takes three functions as arguments. One to convert from a generic event handler (EventHandler<T>) to the specific event handler that the Click event uses (RoutedEventHandler). The conversion is straightforward, because the two event handlers have the same signature. The other two functions add and remove the handler. This extension method can be used like so:

IObservable<Event<RoutedEventArgs>> clicks = button1.GetClicks();

It would not surprise me if we eventually see extra libraries that define many of these extension methods for us. Ideally they would be unnecessary, and we could simply write:

IObservable<Event<RoutedEventArgs>> clicks = button1.GetObservableEvent(b => b.Click);

But unfortunately the dreaded “The event 'System.Windows.Controls.Primitives.ButtonBase.Click' can only appear on the left hand side of += or –=”  message rears its ugly head. Perhaps in C# 5 the story will be different.

Moving on, the code initializes a count variable and subscribes an increment function to the clicks:

int count = 0;
clicks.Subscribe(() => count++);

You’ll notice that I am simply passing an Action to the Subscribe method, rather than an IObserver. The Reactive Framework won’t force you to use an IObserver if all you want to do is call a function when a new event occurs.

Finally, the code converts the stream of click events into a stream of messages and subscribes to it:

IObservable<string> messages = from c in clicks
                               select string.Format("Clicked {0} time{1}", count, count > 1 ? "s" : "");
messages.Subscribe(s => button1.Content = s);

There are lots of other ways in which to rewrite today’s example code. This version is very short and still quite readable:

int count = 0;
button1.GetClicks().Select(x => ++count)
    .Subscribe(() => button1.Content = string.Format("Clicked {0} time{1}", count, count > 1 ? "s" : ""));

Or I could implement IObserver:

public class CountingButtonObserver : IObserver<Event<RoutedEventArgs>>
{
    private int _count = 0;
    public Button Button { get; set; }

    public void OnNext(Event<RoutedEventArgs> value)
    {
        _count++;
        Button.Content = string.Format("Clicked {0} time{1}", _count, _count > 1 ? "s" : "");
    }

    public void OnError(Exception exception) {}
    public void OnCompleted() {}
}

And use it like so:

button1.GetClicks().Subscribe(new CountingButtonObserver{ Button = button1});

Well, that’s probably enough for today. I’m going to continue to explore the Reactive Framework and see what interesting things I can find. Oh, and let me know if you come across any documentation for it – so far I haven’t found any and it can be quite a struggle to make sense of the various extension methods that are available.

I’ll be pushing my experiments with the Reactive Framework to a github repo.

11 comments:

  1. I started to implement my own too :) No interest in Silverlight for now, and it's a very good learning exercise. Thanks for your series, it was a great help with a lot of problems I had.

    ReplyDelete
  2. Hi Paul,
    I've got a listbox databound to a collection.

    Each listbox item must retrieve an image server side shrinked on the fly on an asp.net site where a proper WCF service is used.

    I guess I have to call an asynchronous metod like GetResizedImageAsynch on each ListBoxItem Loaded event....

    ...and then to set the image datasource in a callback function.

    Is that right? Are there concurrency problems like the ones one meets calling WebClient.DownloadStringAsync so we need to define an UserState?

    In that case, is it possible to use TADAAA The Reactive Framework I'm just trying to Understand
    or am I saying a lot of garbage? :)
    Thank you a lot.

    ReplyDelete
  3. Hi Andrea,

    I'm actually very new to silverlight development, so I'm afraid I cannot offer you any advice on your problem. However I would suggest you avoiding using the reactive framework to solve your problems at this point in time because it has not yet had an official release and there is no documentation.

    You should try posting a more detailed explanation of your problem on www.stackoverflow.com - good luck!

    ReplyDelete
  4. Hey Paul,

    It's good to see you're studying Rx. You're asking perceptive questions and making astute observations. Although Rx has only been released for Silverlight someone has modified the assembly to enable it to work with the full framework (Google "Mark WPF Rx framework").

    I wanted to drop in and mention that you can simplify your code with Rx's powerful combinators:

    var numberOfClicksIncreased = button1.GetClick().Select(_ => 1).Scan((x, y) => x + y);
    numberOfClicksIncreased.Subscribe(numberOfClicks => Debug.Write(numberOfClicks));

    Scan works the same way Aggregate does, but returns the intermediary computations. For example new[]{1,2,3}.Aggregate((x,y) => x + y) returns 6, whereas new[]{1,2,3}.Scan((x,y) => x + y) returns 1, 3, 6.

    The trick with Rx (and Linq to objects) is to keep refining your query until it returns _exactly_ the data you need. Only when you iterate over your query (using Subscribe or foreach) should you perform a stateful action like update the UI.

    Keep up the good work. Mastering Rx and Linq is a lot like learning a martial art. It requires discipline but it can make you an incredibly powerful developer.

    ReplyDelete
  5. Hi Jafar, thanks for stopping by! Cheers for the tip on Scan, I suspected it worked similar to Aggregate but I didn't quite understand the distinction, it makes sense now.

    I'm looking forward to reading more great posts on your blog.

    ReplyDelete
  6. Many thanks anyway Paul!
    Keep exploring this new feature, you're doing a great job! I won't miss new articles

    ReplyDelete
  7. Would it be possible or make sense to reimplement the DependencyObject / DependencyProperty system with IObservable/IObserver?

    I'm asking because I'm currently writing a GUI library for game. The library's architecture is inspired by WPF.

    ReplyDelete
  8. Good question. As I understand it, the DependencyObject pattern is used so that the WPF framework can be notified if ANY relevant property changes. The problem with trying to use IObservable in its stead is that you would need one per property, and somehow WPF would need a way to discover them. I suppose you might be able to replace DependencyObject with some sort of "ObservableObject" but at that point you're probably not gaining much. Hope that makes sense.

    ReplyDelete
  9. I am trying to see how I could use this to listen to the "PropertyChanged" event of my ViewModel when a specific Property has changed and update the ItemsSource of my ListBox. Without using BindableLinq I'd like to try this using just Rx. Is it possible? The reason I am doing this is because I want to bind the ItemsSource to a sub-set of the data in the ObservableCollection but when any items in that collection change, it needs to be reflected on the UI.

    var propertyChanges = Observable.FromEvent(this.ViewModel, "PropertyChanged");
    var propertyEvents = from p in propertyChanges
    where p.EventArgs.PropertyName == "MetaData"
    select p;
    propertyEvents.Subscribe( /* What do I put here?? */);

    If there's a better way and I can just bind my ListBox.ItemsSource directly to an "observable" LINQ query my ViewModel's ObservableCollection that would be ideal...

    ReplyDelete
  10. Hi Tim,

    To be honest I'm not sure how you should do it. In the subscribe call, you can basically pass a lambda expression that does whatever you want. This lambda expression could manually update your ListBox, but this goes against the approach that WPF and Silverlight encourage us to take, which is to not bother with manually updating our controls but instead bind them and let them update themselves as necessary via INotifyPropertyChanged. I've done barely any WPF and Silverlight development, so I'm really not sure of how we should be using these with Rx. Sorry I couldn't be of much help.

    ReplyDelete