Writing WPF apps using Kiddo/IronRuby and the MVVM pattern

Kiddo is a Ruby DSL for building simple WPF and Silverlight applications, inspired by the Shoes library.  It’s still an early alpha, but a very cool concept none-the-less.  I rather like the idea of being able to produce a slick looking WPF (or Silverlight) UI using a simple Ruby API.  The basic syntax looks like this (taken from the post linked to above):

require "kiddo"

Kiddo.start do

  draw :stack do
    draw :text, "Text to reverse:", :font_size => 18
    draw :edit_box, :named => "input"
    draw :text, "Your string will show up here.", :named => "result"
    draw :button, "Reverse It!", :named => "fun_button"
  end

  after :fun_button.is_clicked do
    :result.text = :input.text.reverse
  end

end

If you run this, you’ll get something like this on your screen:


You type in some text in the textbox, click the button, and the app will reverse the string and show it above the button.  I think the sample code is fairly self explanatory, but if you’re not sure what it’s doing, check out the link above.

The only issue I had with Kiddo is that it wasn’t clear how one could test the logic that was occurring in one of the event handlers (like the string reversing logic in the “after” clause above).  I wasn’t able to figure out how I could plug in an MVVM (Model View View-Model: the approximate thick-client equivalent of the Model View Controller pattern for web applications) framework without modifying Kiddo, so I went ahead and monkey patched it to support a very basic type of MVVM that would enable me to easily test the logic that handled UI events.  The MVVM extension would need enhancing to be used for probably anything significant, but I think that’s probably true of Kiddo also.  Here is the modified Kiddo example, hooking into the extensions I added…

require "kiddo"
require "kiddo_mvvm"
require "main_view_model"


Kiddo.start(:view_model => MainViewModel) do
  draw :stack do
    draw :text, "Text to reverse:", :font_size => 18
    draw :edit_box, :named => "input"
    draw :text, "Your string will show up here.", :named => "result"
    draw :button, "Reverse It!", :named => "fun_button"
  end 

  command :fun_button.is_clicked, :reverse_text
end

There are three differences between this and the original example.  First, the updated example requires the file with the Kiddo extension I added (kiddo_mvvm.rb) and file with the ViewModel that has the logic that will respond to the button press (main_view_model.rb)  Next, the ViewModel class is associated with the sample UI by passing it in as an option on the Kiddo.start method call.  Finally, instead of having an “after” clause with the actual logic that responds to the button click, we have a call to the new “command” method which ties the button click event (:fun_button.is_clicked) to the method on the ViewModel that should be executed when the event fires (reverse_text).

Here is the code for the ViewModel class containing the logic (main_view_model.rb):

class MainViewModel
  attr_accessor :input
  attr_accessor :result

  def reverse_text
    @result = @input.reverse
    @input = "thank you"
  end
end

It defines public getter/setter properties that correspond to the text of the “input” textbox and the “result” text (label).  It also defines the “reverse_text” method that is invoked by the MVVM extensions to Kiddo in response to the button being clicked.  That method reverses the string in “input” and sets it into “result”.  To illustrate the advantage that this approach has to simply calling a method on a business object in an “after” clause (that the command can easily read from and write to several bound widgets), it also sets “input” to “thank you”.  Now, you can run the revised sample which behaves like the original sample except that it also puts “thank you” in the input box after you click the button.

Now you can write a test directly against MainViewModel to test the logic without bringing the actual UI into play.  Here is a simple example of that (test_MVVM_custom.rb), using the very simple Test::Unit ruby testing framework:

require "test/unit"
require "main_view_model"

class MainViewModelTest < Test::Unit::TestCase
  def test_reversing
    vm = MainViewModel.new
    vm.input = "mytest"
    vm.reverse_text
    assert_equal "tsetym", vm.result
  end
end

The fixture defines a single test that instantiates the ViewModel, sets the input to “mytest”, calls reverse_text and verifies that the result is “tsetym”.  Running a test/unit test is very simple, simply execute the ruby file! (ir test_MVVM_custom.rb)
Finally, here is the code for the MVVM extensions I added to Kiddo (kiddo_mvvm.rb):

module Kiddo
  class App
    attr_accessor :view_model
  end

  class << self
    alias_method :original_start, :start
  end

  def self.start(options={}, &block)
    view_model = options.delete(:view_model)
    if view_model
      App.instance.view_model = view_model.new
    end
    original_start options, &block
  end

  def command(hash, view_model_method)
    view_model = App.instance.view_model
    if !view_model
      raise "Must pass a ViewModel instance to Kiddo.start in order to bind a command"
    end
    hash[:instance].send hash[:method] do |sender, args|
      #Copy data from widgets into ViewModel
      view_model.methods.each do |method|
        if method.index("=") == method.length - 1
          property = method.chop 
                 widget = App.instance.locate property 

                 if widget and defined?(widget.text) 
                   view_model.send method, widget.text 
                 end
        end
      end
      view_model.send view_model_method
      #Copy data from ViewModel back into widgets
      view_model.methods.each do |method|
        if method.index("=") != method.length - 1
          property = method
          widget = App.instance.locate property
          if widget and widget.respond_to?("text=")
            new_text = view_model.send method
            widget.text = new_text
          end
        end
      end
    end
  end
end

This code theoretically shouldn’t need to get modified on an app by app basis (except to enable new types of controls, etc.)  It essentially just adds in support for the “view_model” option on Kiddo.start and then defines the new “command” method.  That method loops through all the methods on the ViewModel that end in “=” (Ruby property setters) and tries to match them to UI widgets based on their name.  When a match is found, it sets the ViewModel property to the text of the corresponding widget.  (Obviously this approach won’t work for all types of widgets, but it’s a start!)  Next, it calls the method on the ViewModel that has the name passed in from the caller.  Finally, it loops through all the methods on the ViewModel that don’t end in “=” and tries to match those to widget names.  When a match is found, it sets the text on the widget to the corresponding property value from the ViewModel.  This enables the ViewModel class to effectively “bind” to widgets simply by creating a getter/setter property with the same name as the widget.

I’m contemplating porting MongoDB Management Studio to use Kiddo, so if that comes to pass, then there will likely be a number of enhancements to this extension.

Print | posted @ Wednesday, June 02, 2010 1:12 PM

Comments on this entry:

Gravatar # re: Writing WPF apps using Kiddo/IronRuby and the MVVM pattern
by Freedom Dumlao at 6/3/2010 7:23 AM

Very cool! I've been spending some time working out better databinding which I think will compliment this approach very nicely.

Your comment:

Title:
Name:
Email:
Website:
 
Italic Underline Blockquote Hyperlink
 
 
Please add 2 and 4 and type the answer here: