Sunday 9 August 2009

Reacting to the Reactive Framework: Part 6

Before I dive into today’s code, there are two things worth mentioning:

  1. If you haven’t already, make haste to Jafar Husain’s blog. Jafar is posting truly interesting examples of using the Reactive Framework - none of the ‘hello world’ fare I’m peddling here.
  2. I mentioned in my previous post that I was having trouble using System.Reactive.dll with non-silverlight projects. Jb Evain has the goods on how you might get around this if you were so inclined. For now I’ll stick with Silverlight as its well suited for including interactive demonstrations in my blog posts.

Today I want to look at combining events using the Reactive Framework (Rx). Here is the demo app for today: (Once again I will remind readers using RSS readers to view this post in a proper browser window so you can see the embedded Silverlight app.)

The idea behind this example is that I am not interested in the selections until an option has been selected from all three groups. From then on, I’d like to be informed whenever the selection changes. This application consists of three instances of a user control, one for each group. Here is the relevant markup and code for the user control:

<StackPanel Margin="20">
    <TextBlock Text="{Binding Heading}" Margin="5" />
    <RadioButton Name="optionButton1" Content="Option 1" />
    <RadioButton Name="optionButton2" Content="Option 2" />
    <RadioButton Name="optionButton3" Content="Option 3" />
</StackPanel>
public ChoiceControl()
{
    InitializeComponent();

    OptionSelections = Observable.Merge(
        Observable.FromEvent<RoutedEventArgs>(optionButton1, "Checked").Select(_ => 1),
        Observable.FromEvent<RoutedEventArgs>(optionButton2, "Checked").Select(_ => 2),
        Observable.FromEvent<RoutedEventArgs>(optionButton3, "Checked").Select(_ => 3)
        );
}

public IObservable<int> OptionSelections { get; private set; }

As you can see, I am creating IObservables from the Checked events of the radio buttons. I’m using the Select method to make observables that simply return the index, and I’m merging those together into one IObservable<int>. So if the user checked option 1, then option 2, then option 3, the OptionSelections would call OnNext on its subscribed observers with values 1, 2 and 3 respectively.

As I mentioned before, this app has 3 instances of this user control:

<StackPanel x:Name="LayoutRoot" Margin="50" Background="Azure" Width="400">
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
        <SilverlightApp:ChoiceControl Name="choiceControl1" Heading="Choice 1"/>
        <SilverlightApp:ChoiceControl Name="choiceControl2" Heading="Choice 2"/>
        <SilverlightApp:ChoiceControl Name="choiceControl3" Heading="Choice 3"/>
    </StackPanel>
    <TextBlock Name="statusText" TextAlignment="Center"></TextBlock>
</StackPanel>

Now somehow I want to subscribe to the OptionSelections from each control, in such a way that I don’t see any results raised until an option from all three groups have been selected. After a few failed attempts, I stumbled upon this solution:

public MainPage()
{
    InitializeComponent();

    string formatString = "Option {0}, Option {1}, Option {2}";

    var selections = choiceControl1.OptionSelections
        .CombineLatest(choiceControl2.OptionSelections, (i, j) => new[] { i, j })
        .CombineLatest(choiceControl3.OptionSelections, (array, k) => new[] { array[0], array[1], k });

    selections.Subscribe(values => statusText.Text = string.Format(formatString, values[0], values[1], values[2]));            

}

I’m using the CombineLatest extension method, but it will only combine two IObservables so I have to use it twice. The first call returns an IObservable<int[]>, where the array contains two elements. I then combine that with the option selections from choiceControl3, again returning an IObservable<int[]>, this time with all 3 selections. The final step is simply to subscribe to the resulting observable and display the message.

This solution of combining once into an array of two elements and then combining again into an array of three doesn’t seem particularly elegant, but so far I haven’t managed to find anything better. Perhaps I could write a version of CombineLatest that takes more than two observables and hides this ugliness? Its probably not necessary – there’s a good chance that the perfect method already exists, and I’ve just missed it.

In my next post, I’d like to demonstrate some of the failed attempts I made before I found CombineLatest. This demo will give me a chance to create my first IObservable<IObservable<T>>, which I’ve been itching to do :)

Today’s code has been tagged in my github repository.

No comments:

Post a Comment