Writing a Searchable Dictionary with Ruby and wxWidgets

Written By: Nathan Baker

- 29 May 2006 -

Description: For today's Ruby lesson, we will explore the creation of a simple GUI, and then create an interface allowing us to search a user-created dictionary.

  1. Introduction
  2. Getting started
  3. Gooey
  4. Making it actually do something

Part 3: Gooey

If you've ever had the misfortune of working with MFC, you will feel right at home with wxWidgets. Essentially, wxWidgets defines a class hierarchy for you, containing classes for things like windows (called 'frames' in wxWidgets terminology), buttons, text areas, and a whole lot of other stuff. You then write your program by inheriting from those classes to make them do what you want (in the case of the widgets, the default behavior is usually acceptable, so there's no need to inherit from, say, a button). In order to get all this, you need to say the following at the top of the file:

 require 'wxruby'
 include Wx

The include Wx line is not strictly required, but if you don't have it then you have to decorate each of the wxWidgets classes with Wx::, and nobody wants that. The starting class is the App class (or Wx::App if you're hardcore about not including other things in the global namespace), which handles the message processing loop for the application. It is not strictly necessary to customize this class at all, but let's go ahead and do so:

class DictionaryApp < App
 
        def on_init
                d = Dictionary.new(ARGV[0])
        end
                
end

Woo, fun! Well, you know what they say about the journey of a thousand miles. Also, don't try running this yet--it won't do anything happy (which brings me to another digression: wxWidgets can cause Ruby to segfault. So no sudden moves). Also, the App class has its own init method which is not initialize but on_init. Whatever, guys, way to completely throw things for a loop

But now that we have this, we can create our frame:

class DictionaryFrame < Frame
        def initialize(dict, parent=nil, id=-1, title='', pos=DEFAULT_POSITION, size=DEFAULT_SIZE)
                style = MINIMIZE_BOX | SIMPLE_BORDER | SYSTEM_MENU | CAPTION
                super(parent, id, title, pos, size, style)
                
                 = dict
        end
end

"Woah, now!" is what you're probably thinking. Where did all that come from? Well, wxWidgets certainly has its share of magic words that you have to know. First off, the parameters to initialize (with the exception of dict) are the parameters that Frame expects. Thus, I use the keyword super to initialize the frame. Note that I don't allow the caller to customize the style (which is the argument that should come after size)--I know better than anyone else what my window should look like, and I want it to be minimizable but not resizable.

Also note that every widget, including forms, are given an ID and have a parent. The ID is for event handling, and passing in -1 will allow the system to assign it an ID automatically. Passing in nil for the parent means that the control is parentless and thus must live in an orphanage. Only your windows should be parentless, and even then you can have one window be the parent of another window if you want. Finally, I assign the dictionary passed as a parameter to the class variable . Nothing fancy.

Oh, and in case you're confused: the < means inheritance.

In order to make the frame display, the on_init method should now look like:

 class DictionaryApp < App
 
        def on_init
                d = Dictionary.new(ARGV[0])
                DictionaryFrame.new(d, nil, -1, 'Dictionary Tool', Point.new(400, 300), Size.new(200, 100)).show
        end
                
end

Also, the top level of the file should now have this code:

DictionaryApp.new.main_loop

You can run it now, and marvel at the pointlessness of it. Also, we are assuming the filename does not have spaces in it. I wrote a filename parser for the previous Ruby article (processing m3u playlists), so you can also use that. Finally, the main_loop function is the message processing function that all GUIs need; it is inherited from the App class.

Believe it or not, we're almost done here. In fact, I'm sure you could do the rest of this yourself if you just knew which magic words to incant. But since I'm already here, I might as well go the rest of the way with you. So, let's put some stuff on that window! The following should be added to the DictionaryFrame class, inside the constructor (def initialize):

 pnlMain = Panel.new(self, -1)
         = TextCtrl.new(pnlMain, -1, '', Point.new(15, 15), Size.new(100,20), TE_PROCESS_ENTER)
        btnLookup = Button.new(pnlMain, -1, 'Look Up', Point.new(120, 15), Size.new(60, 20))
         = StaticText.new(pnlMain, -1, '', Point.new(15,45), Size.new(100, 20))

In wxWidgets, you need something to hold all your widgets. This is a panel. On this panel, we put a textbox, a button, and a label. Note that the button and panel aren't given an @ because we don't need to mess with them after we leave the constructor. The TE_PROCESS_ENTER style passed to the TextCtrl tells it to do something when the enter key is pressed. Feel free to spend a few minutes staring at this code; I know we've been going pretty fast. Like most GUI programming, it's not the concepts that are hard, it's just that you need to do a lot of memorizing (or looking up, more likely) before you are comfortable with how things work. While you're cogitating, I'll start a new section.

<< Previous

Next >>