wxRuby for the Lazy
WxRuby is probably the best overall GUI library for Ruby currently available. It is cross-platform, provides native look-and-feel and is stable enough for production use. All other GUI libraries, despite their various merits, fall short in at least one of the areas. However, WxRuby does have one major downfall. It is pretty much a straight port of the C API. Writing WxRuby code is largely the same as writing actual WxWidgets C code. It’s far from the “Ruby Way”.
So how did I mange to get fairly nice Ruby code despite a binding that is essentially a straight port of the underlying C API? I built it from the bottom-up using a lazy coding technique. And I mean “bottom-up” literally –the following code might actually be easier to read if you start from the bottom and work your way up to the top. The trick is to break down one’s interface into individual widgets and create an instance method for each using the ||= memoization trick.
You can see from the following code I was able to apply this “trick” to everything but toolbar buttons (aka ‘tools’). This is because the toolbar itself is needed to create them. So I simply defined attributes for each tool, but actually created the tool buttons in the toolbar’s method. Have a look.
class View < ::Wx::Frame def initialize super(nil, -1, "My Application", :style => Wx::DEFAULT_FRAME_STYLE | Wx::TAB_TRAVERSAL, :size => [800,600] ) setup_controls setup_events end # kickstart widget creation from the bottom up def setup_controls search_toolbar settings_toolbar search_list end def frame_panel @frame_panel ||= ( panel = Wx::Panel.new(self) panel ) end def frame_sizer @frame_sizer ||= ( sizer = Wx::VBoxSizer.new frame_panel.sizer = sizer sizer ) end def notebook @notebook ||= ( notebook = Wx::Notebook.new(frame_panel) frame_sizer.add(notebook, 1, Wx::GROW) notebook ) end def search_panel @search_panel ||= ( panel = Wx::Panel.new(notebook) notebook.add_page(panel, 'Search') panel ) end def settings_panel @settings_panel ||= ( panel = Wx::Panel.new(notebook) notebook.add_page(panel, 'Settings') panel ) end def search_sizer @search_sizer ||= ( sizer = Wx::VBoxSizer.new search_panel.set_sizer(sizer) sizer ) end def settings_sizer @settings_sizer ||= ( sizer = Wx::VBoxSizer.new settings_panel.set_sizer(sizer) sizer ) end def search_toolbar @search_toolbar ||= ( toolbar = Wx::ToolBar.new(search_panel) @search_start_tool = toolbar.add_tool(-1, 'Start' , Wx::Bitmap.new(DIR + '/images/search.gif', Wx::BITMAP_TYPE_GIF), 'Start') @search_stop_tool = toolbar.add_tool(-1, 'Stop' , Wx::Bitmap.new(DIR + '/images/stop.gif' , Wx::BITMAP_TYPE_GIF), 'Stop') @search_insert_tool = toolbar.add_tool(-1, 'Insert' , Wx::Bitmap.new(DIR + '/images/insert.gif', Wx::BITMAP_TYPE_GIF), 'Insert') @search_import_tool = toolbar.add_tool(-1, 'Import' , Wx::Bitmap.new(DIR + '/images/import.gif', Wx::BITMAP_TYPE_GIF), 'Import') @search_delete_tool = toolbar.add_tool(-1, 'Delete' , Wx::Bitmap.new(DIR + '/images/delete.gif', Wx::BITMAP_TYPE_GIF), 'Delete') search_sizer.add(toolbar, 0, Wx::GROW) toolbar ) end def search_start_tool ; @search_start_tool ; end def search_stop_tool ; @search_stop_tool ; end def search_insert_tool ; @search_insert_tool ; end def search_import_tool ; @search_import_tool ; end def search_delete_tool ; @search_delete_tool ; end def settings_toolbar @settings_toolbar ||= ( toolbar = Wx::ToolBar.new(settings_panel) @settings_save_tool = toolbar.add_tool(-1, 'Save' , Wx::Bitmap.new(DIR + '/images/save.gif' , Wx::BITMAP_TYPE_GIF), 'Save') @settings_undo_tool = toolbar.add_tool(-1, 'Undo' , Wx::Bitmap.new(DIR + '/images/undo.gif' , Wx::BITMAP_TYPE_GIF), 'Undo') @settings_restore_tool = toolbar.add_tool(-1, 'Restore' , Wx::Bitmap.new(DIR + '/images/restore.gif' , Wx::BITMAP_TYPE_GIF), 'Restore') settings_sizer.add(toolbar, 0, Wx::GROW) toolbar ) end def settings_save_tool ; @settings_save_tool ; end def settings_undo_tool ; @settings_undo_tool ; end def settings_restore_tool ; @settings_restore_tool ; end def search_list @search_list ||= ( list = Wx::ListBox.new(search_panel, :choices=>ctlr.sites.map{|s| s.href}) search_sizer.add(list, 1, Wx::GROW) list ) end def setup_events evt_menu( search_start_tool ) { ctlr.search_start } evt_menu( search_stop_tool ) { ctlr.search_stop } evt_menu( search_insert_tool ) { ctlr.search_insert } evt_menu( search_import_tool ) { ctlr.search_import } evt_menu( search_delete_tool ) { ctlr.search_delete } evt_menu( settings_save_tool ) { ctlr.settings_save } evt_menu( settings_undo_tool ) { ctlr.settings_undo } evt_menu( settings_restore_tool ) { ctlr.settings_restore } end end class Controller < ::Wx::App attr :service attr :view def on_init @service = Service.new # backend service @service.connect # connect to database @view = View.new(self) @view.show(true) end def sites ; service.sites ; end def search_start ; service.search_start ; end def search_stop ; service.search_stop ; end def search_insert ; service.search_insert ; end def search_import ; service.search_import ; end def search_delete ; service.search_delete ; end def settings_save ; service.settings_save ; end def settings_undo ; service.settings_undo ; end def settings_restore ; service.settings_restore ; end end Controller.new.main_loop
The thing to notice, if you haven’t caught it yet, is how calling #search_toolbar leads to calling #search_sizer which in turn leads to calling #search_panel, and so forth all the way to the top #frame_panel. This code is a striped down version of actual code I am using. I hope it helps others create wxRuby application more easily. As I said in my previous post, I found in mind-numbingly difficult to create WxRuby interfaces until I worked out this approach. WxRuby is still a difficult API to master, but this technique makes the effort more manageable, and therefore more likely to succeed.
For another example of building structures lazily, have a look at my solution for Ruby Quiz 10 – Crosswords.


Recent comments
1 year 23 weeks ago
1 year 23 weeks ago
1 year 25 weeks ago
1 year 27 weeks ago
1 year 42 weeks ago
1 year 45 weeks ago
1 year 45 weeks ago
1 year 45 weeks ago
1 year 46 weeks ago
1 year 48 weeks ago