Sponsored By Aspose - File Format APIs for .NET

Aspose are the market leader of .NET APIs for file business formats – natively work with DOCX, XLSX, PPT, PDF, MSG, MPP, images formats and many more!

IronRuby and the Reactive Extensions Together Again – Taming User Input

In the previous post, I talked about how IronRuby 1.1 now supports extension methods, and that it not only supports LINQ to objects, but with relative ease it also supports the Reactive Extensions for .NET.  We covered a little example of taking a Ruby Enumerable and turn it into an .NET Observable instance and then projecting and filtering the data.  In this post, we’ll go a little bit further by showing how you can also use events as well and how we can tame user input.

What Does the Future Hold?

Before we continue this series, I thought I’d address the elephant in the room in terms of what the future holds for IronRuby.  Jimmy Schementi, the former PM of the IronRuby project laid out what has been going on in his blog and the IronRuby core mailing list (which if you want to see it stick around, join the community).  Keep in mind that at the time of this writing, no decision has been made one way or the other as to its future.  My hope is that the community does indeed stand up and either continue on IronRuby, or a bridge effort such as RubyCLR as they are both valuable things to have.

Either way, this blog series, however many posts it may be, will continue as I think it might have some value to some folks out there.  So with that, onto today’s subject.

Taming User Input

In this post, I’m going to take an example we had from the Reactive Extensions for .NET Hands on Labs and apply it to a version using IronRuby and WPF.  The idea behind this example is that calls to such things as HTTP calls such as Web Services, REST and so forth can be expensive.  Imagine if you were a really fast typer at over 100 words per minute, and then having each new character cause a new request to be sent.  Chances are you’d overload the system, and not only that, but also have a chance of the results coming back in out of order.  So, how do we fix that?  By using the Reactive Extensions for .NET together with IronRuby, we can make this scenario work quite nicely.

Removing the Boilerplate

Getting started, we’ll need to get some boilerplate out of the way including referencing the proper assemblies for a .NET 4 solution.  In order for us to do so, let’s move the assembly includes to a separate file.  Since we’re going to be using WPF and the Reactive Extensions, we’ll create two separate files, rxnet.rb and wpf.rb.  The rxnet.rb contains all specific assemblies related to the Reactive Extensions such as the following:

load_assembly 'System.CoreEx'
load_assembly 'System.Interactive'
load_assembly 'System.Reactive'

And our wpf.rb contains the following assemblies:

load_assembly 'WindowsBase'
load_assembly 'PresentationCore'
load_assembly 'PresentationFramework'
load_assembly 'System.Xaml'
load_assembly 'System.CoreEx'
load_assembly 'System.Reactive'

Now we can make this available any number of ways, either through a quick gem, or just copying these files directly to the lib folder for IronRuby.  Once we’ve decided on a given path, then we could reference all we need by the following for this post:

require 'System.Core'
require 'wpf'
require 'rxnet'

Now that our boilerplate is out of the way, let’s get on to the solution.

Starting the Solution

Once we have finished that, we are free to include the namespaces and the extension method namespaces which include both System and System::Linq namespaces.  The IObservable extensions for the standard operators reside in System::Linq, and the overloads for the IObservable<T>.subscribe reside in the System namespace.

include System
include System::Linq
include System::Windows
include System::Windows::Controls

using_clr_extensions System
using_clr_extensions System::Linq

Now, let’s create a standard WPF Window class with a StackPanel which contains two elements, our TextBox for entering data and a ListBox for showing the results. 

class RubyWindow < Window

    def initialize
        self.Title = 'Hello Ruby!'
        
        stackpanel = StackPanel.new
        @textbox = TextBox.new
        @listbox = ListBox.new
        stackpanel.children.add(@textbox)
        stackpanel.children.add(@listbox)
        self.content = stackpanel
        
        initialize_handlers
    end

end

Once we’ve done this, now let’s get to the interesting part.  After setting the content of our Window to the StackPanel, let’s hook up the to the TextChanged event of our TextBox by calling Observable.from_event generic method (FromEvent) with the arguments of TextChangedEventArgs. 

def initialize_handlers

    @textbox_changed = Observable.
        method(:from_event).
        of(TextChangedEventArgs).
        call(@textbox, 'TextChanged')

end

This is all fine and interesting, but the TextChangedEventArgs doesn’t actually give us the text of our TextBox, so, let’s fix that to call select, passing in our IEvent<TextChangedEventArgs> argument and then retrieving the text property from the sender, which in this case was the TextBox.  In order for us to call generic methods using IronRuby, we need to make use of the following pattern in which we specify the method we want to call, of what type and then we can invoke it via the call method.

some_object.
    method(:some_method).
    of(SomeType).
    call(some_args)

To see this in action, we’ll apply the same pattern to call the from_event method of type TextChangedEventArgs, and then we invoke via call with our @textbox and the ‘TextChanged’ event.

def initialize_handlers

    @textbox_changed = Observable.
        method(:from_event).
        of(TextChangedEventArgs).
        call(@textbox, 'TextChanged').
        select(lambda { |event| event.sender.text })        

end

One distinct advantage we have here while using a dynamic language such as Ruby, we’re able to get the text from the sender without having to cast.  For example, using C#, we’d have to do the following:

var textChanged = 
    Observable.FromEvent<TextChangedEventArgs>(textbox, "TextChanged")
        .Select(ev => ((TextBox)sender).Text);

We can subscribe to the resulting observable and then have the items added to the ListBox.

def initialize_handlers

    @textbox_changed = Observable.
        method(:from_event).
        of(TextChangedEventArgs).
        call(@textbox, 'TextChanged').
        select(lambda { |event| event.sender.text })        

    @textbox_changed.
        subscribe(lambda {|text| @listbox.items.add(text))

end

This is great, but as you may notice, all of our input is immediately put into the below ListBox.  This may or may not be a problem, but imagine we’re calling an external service, then ultimately it’d be a huge issue to have hundreds of calls going out at once because we’re fast typers.  Let’s fix that by using the throttle method (Throttle) which allows us to specify a timeout time before the event is fired.  If there are no other events that happen in the specified timeframe, then we get the result, else we keep waiting until that timeout.  So, let’s now throttle our input.

def initialize_handlers

    @textbox_changed = Observable.
        method(:from_event).
        of(TextChangedEventArgs).
        call(@textbox, 'TextChanged').
        select(lambda { |event| event.sender.text })        

    @textbox_changed.
        throttle(TimeSpan.from_milliseconds(500))
        subscribe(lambda {|text| @listbox.items.add(text))

end

Now we can run it and watch the results, and it should just work, right?  Not so much…

image

The problem is that once we start throttling our input, we’re launching a new thread to make this happen.  This newly spawned thread cannot access the object to modify the items.  Instead, we have to think about Schedulers, which I’ll cover all of them in a later post, but sufficed to say, we have some options.  The easiest of which is the observe_on_dispatcher method (ObserveOnDispatcher) which allows us to observe this sequence on the control’s dispatcher.  Let’s change the code to fix this issue:

def initialize_handlers

    @textbox_changed = Observable.
        method(:from_event).
        of(TextChangedEventArgs).
        call(@textbox, 'TextChanged').
        select(lambda { |event| event.sender.text })        

    @textbox_changed.
        throttle(TimeSpan.from_milliseconds(500)).
        observe_on_dispatcher.
        subscribe(lambda {|text| @listbox.items.add(text))

end

And now we can start typing, and sure enough our text is throttled, including copying the sentence and pasting it.

image

But you’ll notice that we now have a duplicate because of the copy/paste operation.  What we really want is to have distinct values only in our ListBox.  To make that happen, we’ll need to use the distinct_until_changed method (DistinctUntilChanged) which then eliminates those duplicates.

def initialize_handlers

    @textbox_changed = Observable.
        method(:from_event).
        of(TextChangedEventArgs).
        call(@textbox, 'TextChanged').
        select(lambda { |event| event.sender.text })        

    @textbox_changed.
        throttle(TimeSpan.from_milliseconds(500)).
        observe_on_dispatcher.
        distinct_until_changed.
        subscribe(lambda {|text| @listbox.items.add(text))

end

And we can run the same experiment and sure enough our result is throttled and we receive no duplicates from item to item.

image

And there you have it, a simple case of taming user input, all with the use of IronRuby and the Reactive Extensions for .NET.

Conclusion

With the Reactive Extensions for .NET, we have a nice cross-language support for building a bridge to asynchronous and event-based programming.  Coming with the latest release of IronRuby, we now have the ability, and a rather nice one at that, to consume extension methods that are predefined in other languages, which opens a whole new set of opportunities for interoperability.  In the coming posts, I’ll take a look at what else I can do in this space to see that we can fact help solve some of the hardest problems around asynchronous and event-based programming.

As for the future of IronRuby, best to get involved and help where you can if you want to see it be a success!

So with that, download it, and give the team feedback!

This entry was posted in Event-based Porgramming, Reactive Framework, Ruby. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://codebetter.com/members/Matthew.Podwysocki/default.aspx Matthew.Podwysocki

    @Charles,

    Yes, please do check out the HOLs as we have a lot more to offer there and feedback would be appreciated!

    Matt

  • http://www.charlesstrahan.com Charles Strahan

    That’s pretty cool. Just the other day, I was trying to determine the most concise way to throttle user input, as I needed to hit a DB for auto-completion. Very neat solution.

    I haven’t checked out the HOL – I’ll have to take a look. Thanks for the post, Matt.

    -Charles