Silverlight / IronRuby using controls (Part 2)

I’m sorry I didn’t post the last 2 days, but we’re back with a new post. In my previous posts (1, 2) we didn’t use the files as they are generated by the little DSL script, this was chosen purposely so you would know that you don’t really need those files.

Today we are going to use the files that get generated by the DSL script. We’re going to add some classes to silverlight.rb to enable a nicer api for generating elements from ruby.  We left off last time with some code to generate 3 elements. That code wasn’t the prettiest code I’ve ever seen, apparently John Lam agrees and he has written a little DSL script for generating those elements. In today’s post we’ll be using that little script to generate the StackPanel etc.

Download the completed code

Just a refresher, here’s the code we ended up with the last time.

Code (ruby)
  1. requireSystem.Windows.Controls,’ +
  2. ‘ Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35′
  3. requireSystem.Windows.Controls.Extended, ‘+
  4. ‘Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35′
  5.  
  6. include System::Windows
  7. include System::Windows::Controls
  8.  
  9. class FrameworkElement
  10.         def method_missing(m)
  11.                 find_name(m.to_s.to_clr_string)
  12.         end
  13. end
  14.  
  15. xaml = Application.current.load_root_visual(Canvas.new, "app.xaml")
  16. textblock = xaml.my_textblock
  17.  
  18. panel = StackPanel.new
  19. panel.margin = Thickness.new 50
  20. panel.orientation = Orientation.horizontal
  21.  
  22. button = Button.new
  23. button.content = ‘Push Me’
  24. button.font_size = 18
  25. button.margin = Thickness.new 10
  26.  
  27. waterbox = WatermarkedTextBox.new
  28. waterbox.font_size = 18
  29. waterbox.margin = Thickness.new 10
  30. waterbox.width = 200
  31. waterbox.watermark = ‘Type Something Here’
  32.  
  33. panel.children.add(button)
  34. panel.children.add(waterbox)
  35.  
  36. xaml.children.add(panel)
  37.  
  38. button.click do |sender, e|
  39.         textblock.text = waterbox.text
  40. end

Let’s start replacing that code with something a little nicer. Open up the silverlight.rb file in your app folder and add the require directives at the top of your file. Next we’re going to need the Wpf::Builders module created by John Lam it’s included in the download.  We’re going to need to add the following code in the silverlight.rb file:

Code (ruby)
  1. module Wpf
  2.   module Builders
  3.     def name_collector
  4.       @___name_collector_
  5.     end
  6.  
  7.     def [](name)
  8.       name_collector[name]
  9.     end
  10.  
  11.     def inject_names(obj)
  12.       name_collector.each_pair do |k, v|
  13.         obj.instance_variable_set("@#{k}".to_sym, v)
  14.       end
  15.     end
  16.  
  17.     def evaluate_properties(obj, args, &b)
  18.       obj.instance_variable_set(:@___name_collector_, name_collector)
  19.  
  20.       args.each_pair do |k, v|
  21.         if k == :name
  22.           name_collector[v] = obj
  23.         end
  24.         obj.send :"#{k.to_s}=", v
  25.       end
  26.  
  27.       if obj.respond_to? :name
  28.         name_collector[obj.name] = obj unless obj.name.nil?
  29.       end
  30.  
  31.       obj
  32.     end
  33.  
  34.     def add_object_to_name_collector(collection, obj, args = {}, &b)
  35.       obj = evaluate_properties(obj, args, &b)
  36.       obj.instance_eval(&b) unless b.nil?
  37.       collection.add obj
  38.       obj
  39.     end
  40.  
  41.     def add_class_to_name_collector(collection, klass, args = {}, &b)
  42.       obj = evaluate_properties(klass.new, args, &b)
  43.       obj.instance_eval(&b) unless b.nil?
  44.       collection.add obj
  45.       obj
  46.     end
  47.  
  48.     def assign_to_name_collector(property, klass, args = {}, &b)
  49.       obj = evaluate_properties(klass.new, args, &b)
  50.       obj.instance_eval(&b) unless b.nil?
  51.       self.send property, obj
  52.       obj
  53.     end
  54.   end
  55.  
  56.   def self.build(klass, args = {}, &b)
  57.     obj = klass.new
  58.     obj.instance_variable_set(:@___name_collector_, {})
  59.  
  60.     args.each_pair do |k, v|
  61.       if k == :name
  62.         obj.name_collector[v] = obj
  63.       end
  64.       obj.send :"#{k.to_s}=", v
  65.     end
  66.  
  67.     obj.instance_eval(&b) if b != nil
  68.     obj
  69.   end
  70. end

That module takes care of dispatching the correct property names etc. Now that we have the module we can alter the SilverlightApplication a little bit. Let’s add a method add to that class that will be our entry point into the Wpf::Builders dsl.

Code (ruby)
  1. class SilverlightApplication
  2.  
  3.   def add(klass, args = {}, &b)
  4.     obj = Wpf.build klass, args, &b
  5.     children.add obj
  6.   end

Ok we still need to monkey patch a couple other classes before we’re ready to go. The first thing we see is that StackPanel has a setter method for Margin that takes an instance of Thickness. The property margin is defined on FrameworkElement, so that we can use it on every object that inherits of FrameworkElement. We’re also going to be addressing objects by their name. Not all of them have a name setter method so we’re going to ensure that all of them have one by monkey patching DependencyObject

Code (ruby)
  1. class FrameworkElement
  2.   alias_method :old_margin=, :margin=
  3.   def margin=(value)
  4.     self.old_margin = Thickness.new *value
  5.   end
  6.  
  7.   def method_missing(m)
  8.     find_name(m.to_s.to_clr_string)
  9.   end
  10. end
  11.  
  12. class DependencyObject
  13.   def name=(value)
  14.     self.set_value(FrameworkElement.NameProperty, value.to_clr_string)
  15.   end
  16. end

This gives us the ability to set margins as if it were normal integer values.

The next thing we see is that there is an Orientation property that we’re going to set and that seems to take an enumeration of some sort. To accomplish this we’re going to monkey patch StackPanel.

Code (ruby)
  1. class StackPanel
  2.  
  3.   alias_method :old_orientation= , :orientation=
  4.  
  5.   def orientation=(value)
  6.  
  7.     self.old_orientation= case value
  8.     when :horizontal
  9.       Orientation.horizontal
  10.     when :vertical
  11.       Orientation.vertical
  12.     end
  13.   end
  14. end

We’re almost there don’t worry :). The next thing we see is that we need to be able to add child elements to that element. A StackPanel, DockPanel and so forth are all children of Panel so let’s patch Panel and enable this for more containers. To do this we need to include the module Wpf::Builders in the Panel class and we need to make sure we have all the necessary methods in there that will allow us to add children to that Panel.

Code (ruby)
  1. class Panel
  2.   include Wpf::Builders
  3.  
  4.   def add(klass, args = {}, &b)
  5.     add_class_to_name_collector(children, klass, args, &b)
  6.   end
  7.  
  8.   def add_name(name, obj)
  9.     name_collector[name] = obj
  10.   end
  11.  
  12.   def add_obj(obj)
  13.     add_object_to_name_collector(children, obj)
  14.   end
  15.  
  16. end

We’ve now got us a little DSL for generating the necessary elements for this demo. All that is left to do now is write the initialize method in app.rb so that we get the same result as the last demo. Below we’ve got the content of the app.rb file

Code (ruby)
  1. require "Silverlight"
  2.  
  3. class App < SilverlightApplication
  4.   use_xaml
  5.  
  6.   def initialize
  7.     add StackPanel, :margin => 50, :orientation => :horizontal do
  8.  
  9.       add Button, :content => ‘push me’,
  10.                   :name => ‘my_button’,
  11.                   :font_size => 18
  12.       add WatermarkedTextBox,
  13.                   :font_size => 18,
  14.                   :margin => 10,
  15.                   :name => ‘my_waterbox’,
  16.                   :width => 200,
  17.                   :watermark => ‘Type something here’
  18.     end
  19.  
  20.     my_button.click do |sender, e|
  21.       my_textblock.text = my_waterbox.text
  22.     end
  23.   end
  24.  
  25. end
  26.  
  27. App.new

I hope you can see that Silverlight and IronRuby can really work together to help you build some cool things easily. The next post will deal with some animation, hope to see you there ;)

del.icio.us Tags: ,

Technorati Tags: ,


You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

One Response to “Silverlight / IronRuby using controls (Part 2)”

  1. 64bb507b7a28…

    64bb507b7a281bc695b0…

Leave a Reply