<< Buy This Book >>

Chapter 2

GUI Toolkits for Ruby

Solutions in this chapter:

·         Using the Standard Ruby GUI: Tk

·         Using the GTK+ Toolkit

·         Using the FOX Toolkit

·         Using the SWin/VRuby Extensions

·         Other GUI Toolkits

·         Choosing a GUI Toolkit

·         Summary

·         Solutions Fast Track

·         Frequently Asked Questions

Introduction

Although Ruby is an excellent tool for writing low-level scripts for system administration tasks, it is equally useful for writing end-user applications. And because graphical user interfaces (GUIs) are a must for modern end-user applications, you need to learn how to develop GUIs for Ruby. One of the benefits of Ruby programming that you’ve no doubt come to appreciate is that it enables rapid application development. In contrast to the time-consuming code-compile-test cycle of traditional programming languages, you can quickly make changes to your Ruby scripts to try out new ideas. This benefit becomes all the more evident when you start developing GUI applications with Ruby; it’s both instructive and rewarding to build up the user interface incrementally, adding new elements and then re-running the program to see how the user interface has changed as a result.

You may already know that the standard Ruby source code distribution includes an interface to Tk, which is an established cross-platform GUI toolkit. If you peruse the Ruby Application Archive (RAA) however, you’ll quickly discover that there is a large number of other GUI toolkit choices for use with Ruby (www.ruby-lang.org/en/raa.html). Why wouldn’t you want to stick with Tk if it’s the standard? Well, as you work through this chapter you’ll learn about some of the considerations that might prompt you to look at alternatives. Like all software, these packages are in various stages of development: some are new and unstable, while others are older and quite robust, but most fall somewhere in-between. Most of the GUI toolkits for Ruby are cross-platform, meaning that applications built around them will work on multiple operating systems, while others are targeted towards a single operating system.

Every GUI toolkit has its own unique feel and feature-set, but there are some things that are true of almost any GUI toolkit with which you may work:

·         GUI applications are event-driven. Many programs you’ll write proceed from start to finish in a very predictable path, and for a given set of inputs they’ll produce the same outputs with little or no user intervention. For example, consider a Ruby script written to process a large number of text files in batch mode, perhaps updating selected text in those files. Such a program will always produce the same results for a given set of input files, and it does so without any user intervention.

In contrast, event-driven programs spend most of their time waiting for user inputs (events) that drive the program’s flow.

·         Every toolkit has its own way of communicating these user interface events to your application code, which boils down at some point to your registering certain functions with the toolkit to “handle” the event.

·         GUI toolkits consist of a large number of basic user interface objects (called widgets), like buttons, labels and text fields, as well as more complex widgets, like spreadsheets, calendars or text editors.

·         User interface windows are constructed using a “parent-child” composition; the top-level main window contains one or more child windows; each child window may in turn contain child windows; and so on. This is an application of the Composite pattern, in that operations applied to parent windows (such as hiding it) usually affect the window’s children as well ( they are hiddenas well).

·         GUI toolkits offer one or more geometry (or layout) management options for arranging child windows (or widgets) inside other container windows.

The purposes of this chapter are to introduce some of the most popular GUI toolkits for Ruby, demonstrate how some of the common GUI programming idioms discussed above are implemented, and help you decide which might be the best choice for your applications. To do this, we’ll first describe a simple application that has a lot of features and functionality that you would use in any GUI application. Then we’ll take a look at how you would develop this application in each of the GUI toolkits. Along the way, we’ll discuss other related topics, such as how to download, compile and install the toolkit on your system, and auxiliary tools (such as GUI builders) that can make application development with that GUI toolkit easier. Sources for additional information and resources can be found in the discussion of each respective toolkit.

Using this Book’s Sample Applications

For each of the four major toolkits covered (Tk, GTK+, FOX and SWin/VRuby) we’ll develop a similar sample application so that you can easily identify the similarities and differences among the toolkits while learning how to use them. The application is a simple XML viewer that uses Jim Menard’s NQXML module as its document model, so you’ll need to obtain and install that extension before you can actually run the sample applications on your system. For your convenience, the CD accompanying this book provides the source code for each of the four sample applications:

·         tk-xmlviewer.rb is the Ruby/Tk version of the sample application;

·         gtk-xmlviewer.rb is the Ruby/GTK version of the sample application;

·         fxruby-xmlviewer.rb is the FXRuby version of the sample application; and,

·         vruby-xmlviewer.rb is the VRuby version of the sample application.

To give you a head start on developing your own GUI applications, the user interface for this application will demonstrate the following common features:

·         Displaying a menu bar, with several pull-down menus and choices for opening XML files or quitting the application.

·         Using common dialog boxes, like a file-open dialog, to request information from the user.

·         Using geometry-layout managers to automatically arrange widgets.

·         Using various widgets to display the XML document nodes (or entities) and their attributes.

Using the Standard Ruby GUI: Tk

The standard graphical user interface (GUI) for Ruby is Tk. Tk started out as the GUI for the Tcl scripting language developed by John Ousterhout in the mid-eighties, but has since been adopted as a cross-platform GUI by all of the popular scripting languages (including Perl and Python). Although Tk’s widget set is a bit limited as compared to some of the more modern GUIs, it has the unique distinction of being the only cross-platform GUI with a strong Mac OS port.

Obtaining Tk

One of the primary advantages of using Tk with Ruby is that, because it is the standard, it’s very easy to get started. Developing GUI applications with Ruby/Tk requires both a working installation of Tk itself as well as the Ruby/Tk extension module.

You’re welcome to download the source code for Tk from the Tcl/Tk home page at www.tcltk.org and build it yourself, but precompiled binaries for Tk are available for most operating systems (including Linux and Microsoft Windows). To make life even easier, if you’re running the standard Ruby for Windows distribution from the Pragmatic Programmers’ site (www.pragmaticprogrammer.com/ruby/downloads/ruby-install.html), you already have a working Tk installation. Similarly, most Linux distributions include Tcl/Tk as a standard installation option.

The other piece of the puzzle, the extension module that allows Ruby to access Tk, is included in the Ruby source code distribution. If you built Ruby from its source code, the Ruby/Tk extension was automatically built as well and should be installed along with the rest of the Ruby library on your system. The standard Ruby installer for Windows also includes the Ruby/Tk extension.

Ruby/Tk Basics

Ruby/Tk provides a number of classes to represent the different Tk widgets. It uses a consistent naming scheme and in general you can count on the class name for a widget being Tk followed by the Tk widget name. For example, Tk’s Entry widget is represented by the TkEntry class in Ruby/Tk.

A typical structure for Ruby/Tk programs is to create the main or “root” window (an instance of TkRoot), add widgets to it to build up the user interface, and then start the main event loop by calling Tk.mainloop. The traditional “Hello, World!” example for Ruby/Tk looks something like this:

require ‘tk’

 

root = TkRoot.new

button = TkButton.new(root) {

  text “Hello, World!”

  command proc { puts “I said, Hello!” }

}

button.pack

Tk.mainloop

The first line just loads the Ruby/Tk extension into the interpreter and the second line creates a top-level window for the application. Finally, we get to the interesting part:

button = TkButton.new(root) {

  text “Hello, World!”

  command proc { puts “I said, Hello!” }

}

Here we’re creating a button whose parent widget is the main window. Like all of the GUI toolkits we’ll look at, Ruby/Tk uses a composition-based model where parent widgets can contain one or more child widgets, some of which may themselves be containers. This code fragment also demonstrates one way to specify various configuration options for a widget by following it with a code block. Inside the code block, you can call methods that change aspects of the widget’s appearance or behavior; in this example, the text method is used to set the text displayed on the button, while the command method is used to associate a procedure with the button’s callback (more on this in the next section). An alternate (but equivalent) form for specifying widget options is to pass them as hash-style (key, value) pairs, for the second and following arguments to the widget’s new function, as follows:

button = TkButton.new(root, text => “Hello, World!”,

  command => proc { puts “I said, Hello!” })

The second line is important because it instructs the button’s parent container (the root window) to place it in the correct location. For this example that’s not too difficult, since the root window only has one child widget to deal with. As we’ll see later, real applications have much more complicated layouts with deeply nested structures of widgets contained within other container widgets. For those cases, we’ll pass additional arguments to the widget’s pack method to indicate where it should be placed in the parent container, how its size should change when its parent’s size changes, and other aspects related to the layout.

This example program “ends”, as most Ruby/Tk programs do, with a call to Tk.mainloop; but this is actually where the action begins. At this point, the program loops indefinitely, waiting for new user interface events and then dispatching them to the appropriate handlers. A lot of your work in developing GUI applications with Ruby/Tk is deciding which events are of interest and then writing code to handle those events; this is the topic of the following section.

Creating Responses to Tk’s Callbacks and Events

Tk’s event model is split along two closely-related lines. On one hand, the window system generates low-level events such as “the mouse cursor just moved into this window” or “the user just pressed the S key on the keyboard”. At a higher level, Tk invokes callbacks in your program to indicate that something significant happened to a widget (a button was clicked, for example). For either case, you can provide a code block or a Ruby Proc object that specifies how the application responds to the event or callback.

First, let’s take a look at how to use the bind method to associate basic window system events with the Ruby procedures that handle them. The simplest form of bind takes as its inputs a string indicating the event name and a code block that Tk uses to handle the event. For example, to catch the ButtonRelease event for the first mouse button on some widget, you’d write:

someWidget.bind(‘ButtonRelease-1’) {

  … code block to handle this event …

}

For some event types, it’s sufficient to use the basic event name, like “Configure” or “Destroy”, but for others you’ll want to be more specific. For this reason the event name can include additional modifiers and details. A modifier is a string like “Shift”, “Control” or “Alt”, indicating that one of the modifier keys was pressed. The detail is either a number from 1 to 5, indicating a mouse button number, or a character indicating a keyboard character. So, for example, to catch the event that’s generated when the user holds down the Ctrl key and clicks the right mouse button (sometimes known as Button 3) over a window, you’d write:

aWindow.bind(‘Control-ButtonPress-3’, proc { puts “Ouch!” })

The names of these events are derived from the names of the corresponding X11 event types, for mostly historical reasons; Tcl/Tk was originally developed for the Unix operating system and the X Window system. The Tk ports for Windows, Macintosh and other platforms use the same event names to represent their “native” windowing system events. The sample application we’ll develop later uses a few other event types, but for a complete listing of the valid event names, modifiers and details you should consult the manual pages for Tk’s bind command. A good online source for this kind of reference information is the Tcl/Tk documentation at the Tcl Developer Xchange Web site (http://tcl.activestate.com/doc).

It’s useful to be able to intercept these kinds of low-level events, but more often you’re interested in the higher-level actions. For example, you’d simply like to know when the user clicks on the Help button; you don’t really need to know that the user pressed the left mouse button down on the Help button and then, a few milliseconds later, released the mouse button. Many Ruby/Tk widgets can trigger callbacks when the user activates them, and you can use the command callback to specify that a certain code block or procedure is invoked when that happens. As with any other widget option, you can specify the command callback procedure when you create the widget:

helpButton = TkButton.new(buttonFrame) {

  text “Help”

  command proc { showHelp }

}

or you can assign it later, using the widget’s command method:

helpButton.command proc { showHelp }

Note that since the command method accepts either procedures or code blocks, you could also write the previous code example as:

helpButton = TkButton.new(buttonFrame) {

  text “Help”

  command { showHelp }

}

Some widgets, like TkCanvas, TkListbox and TkText, may not be able to display all of their contents in the space allotted to them. For example, if you’re using a TkText widget to display a very long document, at best you’ll only be able to see a screen’s worth of text at a time. For this reason you’ll typically attach TkScrollbar widgets to one or more sides of the main widget to allow the user to scroll through the widget’s contents. In order to properly interact with scrollbars, widgets like TkText or TkListbox can also generate callbacks when their contents are scrolled horizontally or vertically. To associate code blocks with these callbacks you can use the widget’s xscrollcommand or yscrollcommand