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.
Just a refresher, here’s the code we ended up with the last time.
-
require ‘System.Windows.Controls,’ +
-
‘ Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35′
-
require ‘System.Windows.Controls.Extended, ‘+
-
‘Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35′
-
-
include System::Windows
-
include System::Windows::Controls
-
-
class FrameworkElement
-
def method_missing(m)
-
find_name(m.to_s.to_clr_string)
-
end
-
end
-
-
xaml = Application.current.load_root_visual(Canvas.new, "app.xaml")
-
textblock = xaml.my_textblock
-
-
panel = StackPanel.new
-
panel.margin = Thickness.new 50
-
panel.orientation = Orientation.horizontal
-
-
button = Button.new
-
button.content = ‘Push Me’
-
button.font_size = 18
-
button.margin = Thickness.new 10
-
-
waterbox = WatermarkedTextBox.new
-
waterbox.font_size = 18
-
waterbox.margin = Thickness.new 10
-
waterbox.width = 200
-
waterbox.watermark = ‘Type Something Here’
-
-
panel.children.add(button)
-
panel.children.add(waterbox)
-
-
xaml.children.add(panel)
-
-
button.click do |sender, e|
-
textblock.text = waterbox.text
-
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:
-
module Wpf
-
module Builders
-
def name_collector
-
@___name_collector_
-
end
-
-
def [](name)
-
name_collector[name]
-
end
-
-
def inject_names(obj)
-
name_collector.each_pair do |k, v|
-
obj.instance_variable_set("@#{k}".to_sym, v)
-
end
-
end
-
-
def evaluate_properties(obj, args, &b)
-
obj.instance_variable_set(:@___name_collector_, name_collector)
-
-
args.each_pair do |k, v|
-
if k == :name
-
name_collector[v] = obj
-
end
-
obj.send :"#{k.to_s}=", v
-
end
-
-
if obj.respond_to? :name
-
name_collector[obj.name] = obj unless obj.name.nil?
-
end
-
-
obj
-
end
-
-
def add_object_to_name_collector(collection, obj, args = {}, &b)
-
obj = evaluate_properties(obj, args, &b)
-
obj.instance_eval(&b) unless b.nil?
-
collection.add obj
-
obj
-
end
-
-
def add_class_to_name_collector(collection, klass, args = {}, &b)
-
obj = evaluate_properties(klass.new, args, &b)
-
obj.instance_eval(&b) unless b.nil?
-
collection.add obj
-
obj
-
end
-
-
def assign_to_name_collector(property, klass, args = {}, &b)
-
obj = evaluate_properties(klass.new, args, &b)
-
obj.instance_eval(&b) unless b.nil?
-
self.send property, obj
-
obj
-
end
-
end
-
-
def self.build(klass, args = {}, &b)
-
obj = klass.new
-
obj.instance_variable_set(:@___name_collector_, {})
-
-
args.each_pair do |k, v|
-
if k == :name
-
obj.name_collector[v] = obj
-
end
-
obj.send :"#{k.to_s}=", v
-
end
-
-
obj.instance_eval(&b) if b != nil
-
obj
-
end
-
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.
-
class SilverlightApplication
-
-
def add(klass, args = {}, &b)
-
obj = Wpf.build klass, args, &b
-
children.add obj
-
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
-
class FrameworkElement
-
alias_method :old_margin=, :margin=
-
def margin=(value)
-
self.old_margin = Thickness.new *value
-
end
-
-
def method_missing(m)
-
find_name(m.to_s.to_clr_string)
-
end
-
end
-
-
class DependencyObject
-
def name=(value)
-
self.set_value(FrameworkElement.NameProperty, value.to_clr_string)
-
end
-
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.
-
class StackPanel
-
-
alias_method :old_orientation= , :orientation=
-
-
def orientation=(value)
-
-
self.old_orientation= case value
-
when :horizontal
-
Orientation.horizontal
-
when :vertical
-
Orientation.vertical
-
end
-
end
-
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.
-
class Panel
-
include Wpf::Builders
-
-
def add(klass, args = {}, &b)
-
add_class_to_name_collector(children, klass, args, &b)
-
end
-
-
def add_name(name, obj)
-
name_collector[name] = obj
-
end
-
-
def add_obj(obj)
-
add_object_to_name_collector(children, obj)
-
end
-
-
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
-
require "Silverlight"
-
-
class App < SilverlightApplication
-
use_xaml
-
-
def initialize
-
add StackPanel, :margin => 50, :orientation => :horizontal do
-
-
add Button, :content => ‘push me’,
-
:name => ‘my_button’,
-
:font_size => 18
-
add WatermarkedTextBox,
-
:font_size => 18,
-
:margin => 10,
-
:name => ‘my_waterbox’,
-
:width => 200,
-
:watermark => ‘Type something here’
-
end
-
-
my_button.click do |sender, e|
-
my_textblock.text = my_waterbox.text
-
end
-
end
-
-
end
-
-
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: IronRuby,Silverlight
Technorati Tags: IronRuby,Silverlight
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.
May 9th, 2008 at 9:22 am
64bb507b7a28…
64bb507b7a281bc695b0…