Scott Hanselman

WPF Sample in IronRuby talking via C# to Wesabe

July 23, '07 Comments [12] Posted in eFinance | Programming | Ruby | Web Services | XML | XmlSerializer
Sponsored By

wesabeironrubymoney John Lam and team have released their first drop the IronRubySource code. It's super-pre-alpha so don't hate, but it's looking pretty sweet, if you're a burgeoning Ruby-head like myself. There's incomplete support for some of the cooler Ruby syntax things, but I have full confidence that it's only going to get better. All the underpinnings are there.

John also announce that the team will host IronRuby source on Rubyforge (which is quite the coup, actually) and that they'll be taking external contributions very soon (this being the open part of open source.) 

Not every sample (initially) will look and feel and smell good like Ruby, and this one is no exception. You might remember that last week I did a quicky Wesabe client in C# and put it up on Google Code. We've got four folks up there improving the code, which is cool.

Today, Shady the intern and I decided to do a sample in IronRuby that would call the C# Wesabe client API and display some account data via WPF. This is part of my new plan to take over Money and Quicken's business and you can tell from the hours of intense UI design that Shady and I did, that my plan is inevitable.

We started from ScottGu's HelloWorld sample, and slogged creatively coded our way forward. Here's how it went.

imageFirst, get the IronRuby source and upzip. Open on Visual Studio 2005 command prompt and compile the DLR and IronRuby by running build.cmd.

All the interesting stuff will show up in bin/release, including the IronRuby equivalent of IRB, the interactive Ruby Interpreter.  You can run this from the command line and try things out interactively if you like.

We used Notepad2 with Ruby Support for our editor, although I'm deeply digging "e" the TextMate for Windows and will likely move over to it for my text editor of choice when I start at Microsoft in September.

Next, we tried to add a require statement to bring in the Wesabe library. You run a IronRuby app (today, in this build) by running rbx.exe YourApp.rb. We put our .rb text file in one folder and had rbx.exe in another, so when we added our wesabelib.dll we had to add it to the same folder that rbx.exe was in, otherwise the Assembly load would fail. You can also put things in the GAC and get to them there. Just remember that Fusion will load from the same folder rbx.exe is in, not the folder that your program is in.

For debugging, we used the classic "got here" debugging of old, via the standard MessageBox, so we added a requre for Windows Forms also and a constant as a alias for the MessageBox class:

require 'System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
require 'wesabelib, Version=1.0.0.0'
MessageBox = System::Windows::Forms::MessageBox

As this build is a pre-Alpha, likely created in the dark of the night just hours ago, there's all sorts of weirdness and stuff that didn't "just work."

The naming integration with .NET is persnickety at best. The C# Wesabe client had a lowercase class called "wesaberest" that IronRuby just couldn't see, so I changed it to uppercase and aliased it with a constant.

#this build of IronRuby doesn't like Class Names that are lowercase,
# so I made mine Uppercase
Wesabe = Wesaberest

As I said, The Wesabe client API that we created is written in C#, and returns a array of Account objects. In the click event of a button, we pass in the username and password from our TextBox and PasswordBox respectively and store our Account object.

Our goal is to spin through the list of Account objects in the Array, but there's a few gotchas, again because of the pre-alphaness. First, we can't use for because in Ruby for is a sugar syntax on top of "each" that assumes an each method exists. Each isn't implemented yet, but the plan is to have for syntax just work over IEnumerable things.

Second, we can't index into arrays because the [] operator isn't implemented yet, but we can call GetValue(), which is the same thing.

#in the future we'll be able to index into CLR arrays...
#oneAccount = myAccounts[i]
oneAccount = myAccounts.GetValue(i)

Thirdly, in IronRuby there's FixNum, which is a DLR extension of Integer and there's MutableStrings, that have private StringBuilder. A lot of this will be hidden in the future, but in this build I had to be aware of some of the impedance mismatches as I tried to coerce my numbers into strings for output.

For example, none of these worked, but I expect they will one day.

Lastly, currentBalance is a float (System.Single) and IronRuby wouldn't respect it's ToString method, so I changed the value to a System.Double and all was fine.

Here's some failed attempts at String Concatenation. Notice the "secret" call to to_clr_string. Those should all go away and the Ruby types should marshal cleanly in most cases. Note that I'm assuming a lot, and in some cases basing these assumptions on my chats with John, but also as educated guesses as to how it ought to work.

#Ruby string concatenation works fine...
my_message.content = "scott" + " hanselman"

#Attempt0
my_message.content = oneAccount.name + oneAccount.currentBalance.to_string("C").to_clr_string

#Attempt1
my_message.content = oneAccount.name + "$" + oneAccount.currentBalance.to_s.to_clr_string

#Attempt2
my_message.content = System::String.Concat("$" + oneAccount.currentBalance.to_s.to_clr_string)

#Attempt3
#There's a name conflict when trying to use the StringBuilder:
# System.MemberAccessException: uninitialized constant ScriptModule::Text
s = System::Text::StringBuilder.new
s.Append(oneAccount.name)
s.Append("$")
s.Append(oneAccount.currentBalance.to_s.to_clr_string)
my_message.content = s.to_string

At this point, I just added a new property called "DisplayString" in the Wesabe Account class and moved on. As for the loop we used a while. It's not clean, but it's clean enough:

my_button.click do |sender, args|
   a = Wesabe.getAccounts(text_user.text,text_password.password)
   myAccounts = a.Items
   i = 0
   accounts_listbox.items.clear
   while (i < a.Items.Length)
      oneAccount = myAccounts.GetValue(i) #GetValue is on System.Array

      i+=1 #needed for the while
      
      # Create new label
      my_message = Label.new
      my_message.font_size = 36
      my_message.content = oneAccount.DisplayString

      #Add the Label to the ListBox
      accounts_listbox.items.add(my_message)
   end
end 

There's a couple of ways to make this even more clean, and those ways will come. I'll update this sample as the new features roll into IronRuby.

I think it'd be nice to add some charts and graphs, make the whole thing a ClickOnce app, etc. Probably, for now, it'll be easier in C#, but it was fun to hack it together in today's drop of IronRuby and the interns learned something.

You can get my source for this Ruby App, along with a custom build of the Wesabe client (with my changes) here. Remember to get the IronRuby source, compile it, then copy the Wesabe client to the bin\release folder (or move the .rb files into there along with the Wesabe dll). I've left all the comments in the source for fun.

Technorati Tags: , , , ,

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Tuesday, July 24, 2007 1:11:30 AM UTC
Scott: Check out Netbeans for Ruby IDE goodness:
http://wiki.netbeans.org/wiki/view/RubyBuildInstructions
http://blogs.sun.com/tor/category/Ruby
Tuesday, July 24, 2007 2:44:44 AM UTC
"e" rocks. I switched over from Notepad++ about a month ago and I have been very pleased. The name sucks.. the editor rocks.
Tuesday, July 24, 2007 11:09:02 AM UTC
It's not just IronRuby that doesn't like class names starting with lower case - it's a Ruby Thing. A class name is a constant, and a constant is AnythingStartingWithAnUpperCaseLetter.

Ruby 1.8.6-25, when presented with this:

class lowercaseclass
def do_something_runnable
puts "hello"
end
end

c = lowercaseclass.new

c.do_something_runnable



says this:

class/module name must be CONSTANT

So IronRuby is behaving correctly. Which is what I'm hoping to see continue. It's going to be a fun ride.
Mike Woodhouse
Tuesday, July 24, 2007 8:36:23 PM UTC
Interesting Mike, but the class I was trying to refer to was "external" in that it was in C#...what's the rules in Ruby for bringing in other libraries? What should the rule in IronRuby? Should C# "class Foo" turn into "foo" and System.Xml.XmlDocument become xmlDocument?
Wednesday, July 25, 2007 7:30:10 AM UTC
Regarding the to_clr_string bit: IronPython has its string type behave as much as System.String, something I hope IronRuby will also do (where possible).

E.g. when calling into .NET classes I pass in the Python int type, and it is marshaled to Int32 without me having to think.

Regarding case: I forgot about that Ruby quirk! I guess I'm so used to camel casing it never occurred that people may still lower case their class names.

Again taking a leaf from IronPython, you add a reference to an assembly, and then import the namespace. Case of classes is preserved. I sure don't want to have to mangle System.Xml.XmlDocument into system.xml.xmlDocument in my head in order to be able to create an instance of it in IronRuby :) In the case of a lower case class name : Be able to register a handler to map the .NET class name to a Ruby-valid class name, with this handler being called on as classes are being defined?

E.g.


IronRuby.lowercase_class_name_mapper = proc do |lower_case_class_name|
lower_case_class_name.classify
end


See http://api.rubyonrails.com/classes/Inflector.html#M001088 for an example of a #classify() method added to the Ruby String class. It converts things like "some_lower_case_name" to "SomeLowerCaseName".

Using the handler way, you don't have a hard & fast rule that is unchangeable, yet you can have a default handler that generates camel cased names.

Leaving naming conflicts as an exercise for the user to do in their own custom handler (however, open classes in Ruby may mean you just get unexpected behaviour as someone's "foo" from the newly required library goes and add methods to already existing "Foo".


nexusprime
Wednesday, July 25, 2007 7:37:04 AM UTC
I'm assuming that System.Windows.Forms require could be shortened to require 'System.Windows.Forms' at some stage?
E.g. if you cared about strong names and the assembly version, you'd use the long form.

nexusprime
Thursday, July 26, 2007 11:03:59 AM UTC
Ah, I get it now. What indeed should the rule be?

I don't know (but I'm by no means a Ruby expert) if this has really arisen as an issue before. Typically (or more often than that) we're not used to dealing directly with classes, but with API function calls.

More fun things to look forward to!
Mike Woodhouse
Friday, August 10, 2007 4:20:02 PM UTC
Any idea how we can get around to use XMAL in IronRuby?
dbcuser
Friday, August 10, 2007 4:55:43 PM UTC
I believe you can just call LoadXaml and hand it a file or a string. I was just lazy in this sample.
Friday, August 10, 2007 8:01:51 PM UTC
I tried a simple example
...
my_window.content = LoadXaml("test.xml")
....
I got method missing. I really appriciate if you ever get time to create a small example of xaml in IronRuby.

Thanks again for the great example to start developing on WPF.
dbcuser
Friday, August 10, 2007 10:00:07 PM UTC
Did you try load_xaml?
Monday, August 13, 2007 2:38:37 PM UTC
Yes, it didn't work either.
dbcuser
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.