Home | Ruby | Ruby Graphical User Interface |     Share This Page
Ruby Howto: Graphical User Interface
All content Copyright © 2006, P. Lutus

Introduction | Overview | First Example Program | Second Example Program | Notes | Conclusion

All article source files: rubygui.zip (10KB)

Introduction
As explained more fully here, Ruby is a relatively young language that maximizes programmer productivity. This article shows how to create a GUI frontend for your Ruby programs. It is assumed that the reader is running Linux and has typical development software installed:

  • Ruby itself (this article's examples run on Ruby 1.8.4).
  • A GUI interface designer, either qtdesigner or kdevdesigner (essentially equivalent programs).
  • The Qt libraries, normally installed if the user is running the KDE desktop.
  • The Ruby Qt interface qtrubyinit (test for presence with "which qtrubyinit").
  • A program named rbuic to translate qtdesigner configuration files into Ruby source (test with "which rbuic").
In a typical Linux/KDE installation meant for development work, all the above will likely be present by default.
Overview
This section provides the sequence of steps to create a Ruby GUI program that relies on the Qt libraries. There are other GUI library options not covered here, including GTK2 and the KDE library "Korundum", for which the development procedure is similar.

This is just an overview of the process. The details will be fleshed out in later sections.

  • Create a user interface using qtdesigner and save the user interface data file, an XML file with the suffix ".ui".
  • Use the program rbuic to translate the qtdesigner data file into a Ruby source file.
  • A crucial step: Because the Ruby source file (created by rbuic) will be overwritten each time there is a change in the user interface, create a separate Ruby source file that subclasses the interface class, and write the implementation in the subclass. This creates a useful distinction between interface and implementation as a separate source file.
  • Write the implementation in the subclass.
  • Test, repeat.
Here is the process as a sequence of files and actions, using file names from the first example below:

(qtdesigner) sampledialog.ui -> (rbuic) -> sampledialog_ui.rb -> (subclass) -> sampledialog.rb

First Example Program
Now we will create a simple Ruby GUI program as an example of the process outlined above.
  • Create a user interface using qtdesigner or kdevdesigner.
    For our example, download sampledialog.ui and load it into the designer program.

    This XML data file contains a description of the user's GUI interface design.
  • Run rbuic on the command line to create a Ruby source equivalent to the designer data file.
    $ rbuic sampledialog.ui -o sampledialog_ui.rb

    This will generate the Ruby source file sampledialog_ui.rb, containing the class SampleDialogUI, a Ruby source equivalent of the designer program's description of the GUI interface. For this example, download sampledialog_ui.rb for comparison purposes. Include either the downloaded or generated file in your working directory.
  • Create a Ruby source file to subclass the GUI interface class.

    We need to subclass SampleDialogUI, present in sampledialog_ui.rb, to separate our implementation code from the volatile interface class that is overwritten on each interface design change. For this example, download sampledialog.rb and include it in your working directory.

    Notice in the example source file that the class name "SampleDialog" subclasses "SampleDialogUI" here:

    class SampleDialog < SampleDialogUI
                  

    Notice also that the functions "testButton_clicked" and "closeButton_clicked", present as stubs in the SampleDialogUI class, are redefined ("overridden") in the SampleDialog subclass. This is how we implement each of the "slots" (target functions for actions in Qt) in our GUI, while keeping them separate from the ever-changing SampleDialogUI class as development proceeds:

    
       # signal handlers
       def testButton_clicked(*k)
          show_method_name
          print "Put test code here.\n"
       end
       def closeButton_clicked(*k)
          show_method_name
          close()
       end
                
    • NOTE 1: Because we create same-name function overrides in the subclass for each slot in the superclass, it is a good idea to avoid renaming the slots in the superclass after creating the overrides. The subclass functions won't automatically rename themselves for each change in the superclass. This could lead to some confusing bugs if this issue is ignored.

    • NOTE 2: In Ruby, one can redefine methods in a class by simply declaring the class again, and we could have used this method to override the desired functions without subclassing. The reason I chose the subclassing approach is because there may be a need to write particular instructions into the initialization function that would prevent the superclass initialization from working as it should. As it turns out, the next program in this tutorial requires this separation of classes.

    • NOTE 3: To avoid confusion. I suggest adopting the habit of naming the GUI interface class "ClassNameUI", and the implementation subclass be named "ClassName", as shown in this example.

  • At this point, run sampledialog.rb to see the result. Click the dialog buttons while examining the text printed to the command-line interface, also try clicking the program frame to close it (rather than using the "Close" button) and notice that the close action is successfully captured for each program closing method. Example:

    $ ./sampledialog.rb
    Command routed to SampleDialog.testButton_clicked
    Put test code here.
    Command routed to SampleDialog.closeButton_clicked
    Command routed to SampleDialog.close
                
Second Example Program
Here is an example program that actually does something useful — it's a regular expression tester. It can be used to interactively experiment with regular expressions and a user-selected example block of text.

Rather than explicitly go through the steps as in the first example, I will simply list the required files — download them and put them into a suitable working directory:

Download the listed files (or get them all at once by downloading rubygui.zip) and put them into a suitable directory. Run regexp_tester.rb:

$ ./regexp_tester.rb
        
Play with the program, preferably while examining the source code. Notice that, if you load a data file (File ... Load) of sample text, or make changes to the options, then exit the program, the data file and the changes are restored the next time the program is run.

This program is a larger version of the first example, and uses the same overall strategy — an implementing subclass for an interface superclass. Notice the various superclass functions that have been overridden in the subclass:

$ grep "(*k)" regexp_tester_ui.rb | sort
    def exitAction_activated(*k)
    def fileAbout_activated(*k)
    def fileReadAction_activated(*k)
    def regexExecute(*k)
    def regexReplace_returnPressed(*k)
    def regexSearch_returnPressed(*k)
        

Remember, while designing your own implementing subclasses and overriding superclass action functions, that the function names must be transferred from the superclass to the subclass and fleshed out there.

Examine the regexp_tester.rb source listing to see how "close()" is handled. In a program like this, where there are some mandatory exit actions, we must capture and act on any action that closes the program, including directly closing the frame that contains the program. To do this, we must override the "close()" function and perform our own cleanup.

Also, because this is a Qt application, having overridden "close()" and performed our own cleanup, directly exiting the Ruby code isn't enough. To avoid improperly abandoned resources and an error message (Mutex destroy failure: Device or resource busy), we must formally exit the Qt application. That, in turn, means we must pass a reference to the application object to our subclass. That, in turn, means we have to write our own initialization function, different from that needed by the interface superclass. And that, in turn, means we have to subclass the interface class, rather than simply writing overrides into a single class.

This program, as simple as it is, creates its own configuration file in (user home)/.RegExpTester/RegExpTester.ini. This file is read and written by the ConfigurationHandler class, which in turn relies on a simpler Configuration class that contains the data to be preserved. This means your option settings, and your choice of test data file, will be preserved between program runs.

For my Ruby GUI example, I decided to write a program that I actually need. I am constantly experimenting with regular expressions, usually by writing a short script that loads sample data, or by using the irb interactive Ruby program if the example text isn't too complicated. This program allows you to test regular expressions interactively, and quickly, before committing to code.

Notes
  • When designing interfaces with either kdevdesigner or qtdesigner, avoid mixing Qt widgets and KDE widgets. It may be possible to do this, but I haven't figured out how. Consistently use one class of widgets or the other.

  • Again, because action function names are mirrored in the subclass, create reasonable names for the interface actions and avoid renaming them after they have been implemented. At the least, keep in mind that this is a likely source of difficulty once the impulse to rename something can no longer be resisted. The symptom is that an action that had previously been implemented will suddenly produce a message:

    ProgramNameUI.function_name_activated(): Not implemented yet

  • For clean, robust program exits, try to follow the pattern shown in these examples. Pass a Qt application reference to the implementing subclass and perform the exit through the application object.

  • If you don't have a Ruby script beautifier (an indentation utility that also detects trivial syntax errors), download mine here. Nothing improves my morale like seeing a properly indented source listing.

Conclusion
Creating a simple GUI interface for Ruby programs is not difficult. I have seen a number of examples of hand-coded user interfaces, but these are rather inflexible and become more so as the complexity of the interface increases. An approach using qtdesigner and rbuic can lead to some rather sophisticated Ruby GUI designs.

 

Home | Ruby | Ruby Graphical User Interface |     Share This Page