

Tuesday, November 27, 2012

JRuby Swing GUIs with cheri gem

PocketMew by Sin Amigos from flickr (CC-BY)

I am not in any way a fan of desktop GUI toolkits - HTML5 and jQuery totally spoiled me, so I resisted for a very long time making GUIs for my Total War tools - and happily enough, other people would sometimes make them for me.

But this time I decided to make a desktop GUI, in JRuby, and that means one of the awful non-HTML toolkits.

So my first idea was of course making a big window with a menu calling some functions, and big embedded HTML form with all stuff in HTML. I was even getting somewhere since Java Swing has HTML widget, but then it turned out it's HTML 3.2 only, no Javascript whatsoever, and serious pain to get data into and out of it.

I also tried SWT, and hoped danlucraft's cookbook would help me get somewhere with it, but I couldn't figure out most of the things I wanted to try, so I kept looking.

Finally I found this lovely cheri gem, which didn't seem to such too hard. I've heard mostly horrible things about Swing API, but it was only as bad as the rumor says, at least for my simple use case.

I'll put all that code for public view eventually, but it's pretty massive, so here are just some tips for working with Swing and cheri.

Basic window creation

Start a class and include Cheri::Swing module. What you probably want to match HTML-ish behaviour is actually not a single layout manager but GridBagLayout (for actual layout) within ScrollPane (so you get scrollbars when content).

That's the code:

class ConcentratedVanillaBuilder
  include Cheri::Swing

  def initialize
    @controls = {}
    @frame = swing.frame('Concentrated Vanilla builder'){ |frm|
      size 800, 800
      default_close_operation :EXIT_ON_CLOSE
      scroll_pane {
        panel {
          grid_table {
            background :WHITE
    load_settings! load_settings_file("settings/default.txt")
    @frame.visible = true

This initialization is pretty generic (other than trivial matters of default window size and title), other than four italicized lines.

Separate form buildings from settings

That's advice for GUIs that simply configure some settings and then run some script. You want to keep your settings in a nice Hash, and don't mix GUI code with settings defaults.

So what you want are helper methods like these:
  def checkbox(name, description)
      @controls["checkbox-#{name}"] = swing.check_box description, :a => :w, :gridwidth => 3

And then use methods on @controls[something] to both get and set various fields. That's far easier than ton of on_change callbacks or whatever is their Swing equivalent.

Use text_area not label for labels

Label widgets are pretty dumb, and non-editable text areas can handle things like multiline text and formatting a lot better.

Just add some helper methods and pretend you're coding HTML:

  def div_helpmsg(msg)
      text_area(:a => :w, :gridwidth => 3){
        editable false
        text msg.gsub(/^\s+/, "")

  def h1(msg)
    font ='Dialog', java.awt.Font::BOLD, 24)
        set_font font
        editable false
        text msg
If things get too complicated, you can always go for full HTML widgets.


Half-finished result looks something like this. Not amazing, but it will do the trick.

It will get released sometime soon, and then you'll be able to play random scenarios everybody's waiting for.

By the way if any Java / JRuby experts has better ideas, go ahead. Googling was unusually unhelpful to me here, and IRC and StackOverflow were as useless as they always are.


Kevin McCaughey said...

Any update on this Tomasz? I was considering this approach but cheri seemed dead and I am a bit hesitant to start on it.

I just wish we had a good GUI & builder that was actually current and developed ;)

taw said...

Kevin: The underlying Java APIs (swing/awt) aren't really evolving much, so I don't think it's a huge deal that cheri doesn't see much development these days.

You can always drop down to low level APIs if cheri doesn't provide something, or fork it on github and make your own version.