# Building a WPF Application in IronRuby

| Posted by Edd Morgan 0 Followers on May 25, 2010. Estimated reading time: 12 minutes |

A note to our readers: As per your request we have developed a set of features that allow you to reduce the noise, while not losing sight of anything that is important. Get email and web notifications by choosing the topics you are interested in.

In my previous post I introduced IronRuby. I expanded a little on the basics of IronRuby by explaining what little extra needs to be done to a Rails app to get it moving on this .NET implementation of the Ruby language — but there wasn't much to it! So now I would like to progress from talking about IronRuby's compatibility with your existing projects to developing a whole new application demonstrating the interoperability between IronRuby and .NET. In particular, we'll be using WPF (Windows Presentation Foundation) — the component of the .NET Framework stack used to create rich media and graphical interfaces.

## Foundations of WPF

To reiterate, WPF is the engine in the .NET Framework responsible for rendering rich user interfaces and other media. It's not the only collection of libraries in the framework with the power to do this — Windows Forms does the trick, too — but WPF is useful when you need to employ eye candy and create impact. Whether you're presenting a document, video, a data entry form, some kind of data visualisation (which I am most hopeful for, especially in terms of IronRuby - more on that later) or chaining all of the above with some flashy animations, you're likely to find that WPF gives you what you need when developing any of these for a Windows target.

Let's demonstrate this with an example. One day, over my lunch break, I created a WPF-based analogue clock - what I like to consider the 'hello, world' of WPF applications - using IronRuby. Any normal person would have just looked at their watch.

Note: It may help to have the WPF documentation handy when working through this example.

## The Sample Application

require 'WindowsBase'
require 'PresentationFramework'
require 'PresentationCore'
require 'System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'

class Clock

CLOCK_WIDTH     = 150
CLOCK_HEIGHT    = 150
LABEL_HEIGHT    = CLOCK_HEIGHT / 7
LABEL_WIDTH     = CLOCK_WIDTH / 7
MIN_LOCATIONS   = {}
HOUR_LOCATIONS  = {}

def run!
plot_locations

# build our window
@window = System::Windows::Window.new
@window.background = System::Windows::Media::Brushes.LightGray
@window.width = CLOCK_WIDTH * 2
@window.height = CLOCK_HEIGHT * 2
@window.resize_mode = System::Windows::ResizeMode.NoResize

@canvas = System::Windows::Controls::Canvas.new
@canvas.width = CLOCK_WIDTH
@canvas.height = CLOCK_HEIGHT

# create shapes to represent clock hands
@minute_hand = System::Windows::Shapes::Line.new
@minute_hand.stroke = System::Windows::Media::Brushes.Black
@minute_hand.stroke_thickness = 1
@minute_hand.x1 = CLOCK_WIDTH / 2
@minute_hand.y1 = CLOCK_HEIGHT / 2

@hour_hand = System::Windows::Shapes::Line.new
@hour_hand.stroke = System::Windows::Media::Brushes.Black
@hour_hand.stroke_thickness = 3
@hour_hand.x1 = CLOCK_WIDTH / 2
@hour_hand.y1 = CLOCK_HEIGHT / 2

# .. and stick them to our canvas

plot_face # draw a clock face
plot_labels # draw clock numbers
plot_hands # draw minute / hour hands

@window.content = @canvas
app = System::Windows::Application.new
app.run(@window)
# the Application object handles the lifecycle of our app
# including the execution loop
end

# determine 2 sets of equidistant points around the circumference of a circle
# of CLOCK_WIDTH and CLOCK_HEIGHT dimensions.
def plot_locations
for i in (0..60) # 60 minutes, and 12 hours
a = i * 6
coords = [x, y]
HOUR_LOCATIONS[i / 5] = coords if i % 5 == 0 # is this also an 'hour' location (ie. every 5 minutes)?
MIN_LOCATIONS[i] = coords
end
end

# draws a circle to represent the clock's face
def plot_face
extra_x = (CLOCK_WIDTH * 0.15) # pad our circle a little
extra_y = (CLOCK_HEIGHT * 0.15)
face = System::Windows::Shapes::Ellipse.new
face.fill = System::Windows::Media::Brushes.White
face.width = CLOCK_WIDTH + extra_x
face.height = CLOCK_HEIGHT + extra_y
face.margin = System::Windows::Thickness.new(0 - (extra_x/2), 0 - (extra_y/2), 0, 0)
face.stroke = System::Windows::Media::Brushes.Gray # give it a slight border
face.stroke_thickness = 1
System::Windows::Controls::Canvas.set_z_index(face, -1) # send our circle to the back
end

# at each point along the hour locations, put a number
def plot_labels
HOUR_LOCATIONS.each_pair do |p, coords|
unless p == 0
lbl = System::Windows::Controls::Label.new
lbl.horizontal_content_alignment = System::Windows::HorizontalAlignment.Center
lbl.width = LABEL_WIDTH
lbl.height = LABEL_HEIGHT
lbl.content = p.to_s
lbl.margin = System::Windows::Thickness.new(coords[0] - (LABEL_WIDTH / 2), coords[1] - (LABEL_HEIGHT / 2), 0, 0)
lbl.padding = System::Windows::Thickness.new(0, 0, 0, 0)
end
end
end

def plot_hands
time = Time.now
hours = time.hour
minutes = time.min

if !@minutes || minutes != @minutes
@hours = hours >= 12 ? hours - 12 : hours
@minutes = minutes == 0 ? 60 : minutes
# Dispatcher.BeginInvoke() is asynchronous, though it probably doesn't matter too much here
@minute_hand.x2 = MIN_LOCATIONS[@minutes][0]
@minute_hand.y2 = MIN_LOCATIONS[@minutes][1]
@hour_hand.x2 = HOUR_LOCATIONS[@hours][0]
@hour_hand.y2 = HOUR_LOCATIONS[@hours][1]
})
end
end

end

clock = Clock.new

timer = System::Timers::Timer.new
timer.interval = 1000
timer.elapsed { clock.plot_hands }
timer.enabled = true

clock.run!

Let it be said that this is by no means perfect, but I think it does the job of illustrating IronRuby's interoperability with WPF using a familiar data visualisation. I'm sure you'll want to dissect the code yourself, but allow me to step through the important bits. (By the way, feel free to run this through ir first to see what actually happens).

Now we're using IronRuby - unlike my previous post where we took pure Ruby code and ran it through ir, the IronRuby interpreter, to demonstrate compatibility. The main thing of note is, of course, the very distinct parallels between .NET namespaces and Ruby modules, .NET classes and Ruby classes. I guess there's not much to say about it other than at this point, you may as well be working with a purely Ruby graphics-drawing library.

You're instantiating .NET objects, but you're doing it with the standard Ruby .new method you know from Ruby as Object#new. You're calling methods on these objects (and classes, for example in the call to System.Windows.Controls.Canvas.SetZIndex()) using the underscored, lowercase convention established for the Ruby language. The integration is so seamless, the surprise of the fact that you're using a dynamic language on top of .NET's CLR (Common Language Runtime, which needs some help from the Dynamic Language Runtime to support dynamic languages) is completely abstracted from you, allowing you to just build your software.

Note: When working with IronRuby, the .NET stack really does integrate at all levels. One thing to note is that root object of all your IronRuby objects isn't actually Object, its System.Object.

## Events

Events are a big part of developing client applications in .NET as well as under every other environment I can think of. In case you aren't aware, event-driven programming is essentially the practice of telling your code to call a particular method, or other chunk of code (a delegate) when something happens at an unpredictable time. You can never predict when a user is going to click a button, strike a key or perform any other kind of input, so the advent of the GUI is what neccessitated event-driven programming.

This is where one of my favourite aspects of the Ruby language, blocks, can really help us. In traditional C#, for instance, you may subscribe to an event (assign a block of code to execute when an event occurs) in one of two ways: by passing a reference to a named method, or by providing an anonymous code block. You'd be right for seeing the parallel with Ruby's concept of blocks, Procs and lambdas here. As demonstrated at the very end of this rather basic script, we are using .NET's System.Timers.Timer to (attempt to) update the clock every second (I know it's probably not the best way of doing this, but for example's sake).

Note: Diverting a little from what I said above, the ticking of a clock is very predictable, yet we still use the event our Timer throws to do this updating as one of many ways to perform that task outside of the main thread.

You'll see that all that's needed to assign a block of code to be triggered on an event is to provide that block to the method of the name of the event as it is known to the CLR. This drawback to this is that it only allows the delegation of one code block to each event. You may use the add method to subscribe multiple handlers to that event - pushing that to the end of a queue. Like so:

def tick
puts "tick tock"
end

timer.elapsed.add proc { puts "tick tock" }
tick_handler = lambda { puts "tick tock" }
timer.elapsed.add(tick_handler)

The ability to just provide a block of code as an event handler helps IronRuby towards a concept that is important to a good dynamic language; low ceremony — reducing the amount of boilerplate code. Anonymous methods are, of course, available in other more conventional .NET languages such as C# and VB but, as usual, feel ever so much more elegant and natural in IronRuby.

Note: Whether it's a named method or an anonymous chunk o' code, the block you delegate to the handling of an event can take arguments - commonly, a sender object and some args.

## XAML & IronRuby

XAML is Microsoft's XML-like language for defining CLR objects and their properties mainly for use in WPF and Silverlight applications. With it, you can declaratively create an entire UI, complete with hook-ups to events in your procedural code and bindings to data only present at run-time; create graphics; and even storyboard animations for those graphics. I'm not going to go into detail on how XAML is structured, but if you have any experience with XML-based languages you should have no problem understanding what's happening.

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Rectangle x:Name="mySquare" Width="50" Height="50">
<Rectangle.Fill>
<SolidColorBrush Color="Green" />
</Rectangle.Fill>
</Rectangle>
<TextBlock Text="Hello, world">
<TextBlock.Foreground>
<SolidColorBrush Color="Red" />
</TextBlock.Foreground>
</TextBlock>
</StackPanel>
</Window>

Note: Window, StackPanel, TextBlock, SolidColorBrush and Rectangle are all WPF classes. Anything you write in your XAML code can just as easily be implemented programmatically in C#, VB or indeed IronRuby.

This code represents a solitary window of indeterminate size. Inside that window is a certain object called a "StackPanel", which is a type of WPF control used to define how it's child controls will flow in terms of layout. Inside that StackPanel are two different objects: a block of text (TextBlock) and a rectangle shape. Objects defined in XAML can be either named for later reference, or anomymous (our Rectangle is named 'mySquare', whereas the TextBlock has not been given a name). These objects' properties can also be given values in two ways: as XML element attributes (e.g. Width="50") or as their own child elements when the expected value is of a non-primitive type (e.g. <Rectangle.Fill> expects a Brush (or a derivative of Brush)).

Without going any further into the labyrinth that is WPF and XAML, because one could easily write volumes on it, let's run this with IronRuby.

require 'PresentationFramework'
require 'PresentationCore'

System::Windows::Application.new.run(@window)

WPF's Application.Run method takes a Window as it's argument. Looking back to our XAML, you can see that our root element is a Window, and that is what gets returned after parsing. All of the controls defined within the XAML are returned as a tree of controls reflecting the structure of the XAML document, with the Window as the root, StackPanel as the only child of Window, Rectangle and TextBlock as the children of StackPanel, etc. You can also address named controls like so:

@window.find_name("mySquare").class # => "System::Windows::Shapes::Rectangle"

## A Side-note on Inheriting from CLR Classes

We've covered compatibility, interoperability but only briefly extendability. I think I have made clear how closely IronRuby integrates with .NET - even to the point where you can extend your CLR classes using inheritance. To demonstrate this, I'd like to revisit the Person class we built in C# in my previous article.

namespace MyClassLibrary
{
public class Person
{
public string Name { get; set; }

public string Introduce()
{
return String.Format("Hi, I'm {0}", Name);
}
}
}

Let's extend it using Ruby, and model a programmer and their drinking habits.

require 'MyClassLibrary.dll'

class Programmer < MyClassLibrary::Person
ACCEPTABLE_DRINKS = [:coffee, :tea, :cola, :red_bull]

def drink(liquid)
if ACCEPTABLE_DRINKS.include? liquid
puts "Mmm... #{name} likes code juice!"
else
raise "Need caffeine!"
end
end
end

me = Programmer.new
me.name = "Edd"
puts me.introduce
me.drink(:coffee)

## A Brief Note on Verbosity

Personally, I don't mind verbose chaining of references in my code as long as it doesn't interfere with performance - as evidenced in the example above. While I love clean code, there's a certain feeling of safety that comes with the terse explicitness of long-winded addressing and the describing of objects as opposed to ambiguity (not unlike this sentence). However, when working with IronRuby, even I grow tired of typing System::Whatever::Something. Some people enjoy simply assuming namespaces and forgetting about them, regardless of the language they're using. Don't worry, IronRuby has those guys covered.

Because .NET namespaces are modules in IronRuby it is completely possible to, with a call to include, bring the contents of a .NET namespace into context of your IronRuby code - just as you would if you wanted to bring in an 'organic' Ruby module. To refactor the style of the above example, I could place the following at the top of my Clock class:

class Clock
include System::Windows::Shapes
include System::Windows::Media
# and so on...

And by doing so, reduce calls to System::Windows::Shapes::Ellipse.new to simply Ellipse.new or references to System::Windows::Threading::DispatcherPriority.Render to a friendlier DispatcherPriority.Render.

Another way to DRY up your IronRuby code and deal with these very long names that occur frequently in the .NET framework is by using namespace aliasing, achievable with an assignment.

require 'System.Windows.Forms'

WinForms = System::Windows::Forms
WinForms::Form.new
WinForms::Label.new

## Conclusion

I hope by now you can understand better how IronRuby interoperates with .NET and how you can harness the .NET framework with the dynamic nature and elegant idioms of the Ruby language.

The manner and parlance of Ruby that makes it a joy to work with sets of data is, of course, present in IronRuby — couple that with WPF's capability to produce graphics and I hope you can visualise the possibilities of data visualisation using these two things. Using IronRuby to create visual representations of data and infographics is very exciting to me. Although today, with this project, we're only presenting one simple piece of information - the time - the potential is grand.

Style

## Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

## Get the most out of the InfoQ experience.

### Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

IronRuby is interesting again

Thanks for the article, I feel .NET application development using IRuby is a breeze.

Re: IronRuby is interesting again

Great, I'm glad you liked it. Personally, I think .NET development itself is a breeze across the board, but it's great to be able to approach solutions in a dynamic fashion with things like IronRuby and IronPython.
Close

#### by

on

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

2 Discuss

Login to InfoQ to interact with what matters most to you.