Section 12.2. RubyGTK2


12.1. Ruby/Tk

The roots of Tk go back as far as 1988, if you count prerelease versions. It was long thought of as a companion of Tcl, but for several years it has been used with several other languages, including Perl and Ruby.

If Ruby had a native GUI, Tk would probably be it. It is still in widespread use at the time of this writing, and some Ruby download versions include Tk in a more or less turnkey fashion.

The previous reference to Perl is not entirely gratuitous. The Tk bindings for Ruby and Perl are similar, enough so that any reference material for Perl/Tk should be mostly applicable to Ruby/Tk. One such reference is Learning Perl/Tk; ISBN: 1565923146 by Nancy Walsh.

12.1.1. Overview

In 2001, Tk was probably the most common GUI in use with Ruby. It was the first one made available and has long been part of the standard Ruby installation. Though it is probably not as popular as it was then, it is still in wide use.

Some say that Tk is showing its age; for those who like clean, object-oriented interfaces, it may be something of a disappointment. But it has the advantages of being well-known, portable, and stable.

Any Ruby/Tk application must use require to load the tk extension. Following that, the application's interface is built up piecemeal starting with some kind of container and the controls that populate it. Finally, a call to Tk.mainloop is made; this method captures all the events such as mouse movements and button presses and acts on them accordingly.

require "tk" # Setting up the app... Tk.mainloop


As with most or all windowing systems, Tk graphical controls are called widgets; these widgets are typically grouped together in containers. The top-level container is called the root; it is not always necessary to specify an explicit root, but it is a good idea.

Every widget class is named according to its name in the Tk world (by appending Tk to the front). Thus the Frame widget corresponds to the TkFrame class.

Widgets are naturally instantiated using the new method. The first parameter specifies the container into which the widget is placed; if it is omitted, the root is assumed.

The options used to instantiate a widget may be specified in two ways. The first (Perl-like) way is to pass in a hash of attributes and values. (Recall that it is a quirk of Ruby syntax that a hash passed in as the last or only parameter may have its braces omitted.)

my_widget = TkSomewidget.new( "borderwidth" => 2, "height" => 40 ,                               "justify" => "center" )


The other way is to pass a block to the constructor that will be evaluated with instance_eval. Within this block, we can call methods to set the attributes of the widget (using methods that are named the same as the attributes). Bear in mind that the code block is evaluated in the context of the object, not the caller. This means, for instance, that the caller's instance variables cannot be referenced inside this block.

my_widget = TkSomewidget.new do               borderwidth 2               height 40               justify "center"             end


Three geometry managers are available with Tk; they all serve the purpose of controlling the relative size and placement of the widgets as they appear onscreen. The first (and most commonly used) is pack; the other two are grid and place. The grid manager is sophisticated but somewhat prone to bugs; the place manager is the most simpleminded of all because it requires absolute values for the positioning of widgets. We will only use pack in our examples.

12.1.2. A Simple Windowed Application

Here we'll demonstrate the simplest possible applicationa simple calendar app that displays the current date.

For good form, we'll begin by explicitly creating a root and placing a Label widget inside it.

require "tk" root = TkRoot.new() { title "Today's Date" } str = Time.now.strftime("Today is \n%B %d, %Y") lab = TkLabel.new(root) do         text str         pack("padx" => 15, "pady" => 10,              "side" => "top")       end Tk.mainloop


In the preceding code we create the root, set the date string, and create a label. In creating the label, we set the text to be the value of str, and we call pack to arrange everything neatly. We tell pack to use a padding of 15 pixels horizontally and 10 pixels vertically, and we ask that the text be centered on the label.

Figure 12.1 shows what the application looks like.

Figure 12.1. A simple Tk application.


As we mentioned, the creation of the label could also be done in this way:

lab = TkLabel.new(root) do         text str         pack("padx" => 15, "pady" => 10,              "side" => "top")       end


The units for screen measurement (as used in this example for padx and pady) are in pixels by default. We can also work in another unit by appending a suffix onto the number; the value now becomes a string, of course, but since Ruby/Tk doesn't care about that, we don't care, either. The available units are centimeters (c), millimeters (m), inches (i), and points (p). All of these are valid padx calls:

pack("padx" => "80m") pack("padx" => "8c") pack("padx" => "3i") pack("padx" => "12p")


The side attribute doesn't actually contribute anything in this case since we have set it to its default. If you resize the application window, you will notice that the text "sticks" to the top part of the area in which it lives. Other possible values are right, left, and bottom, as you might expect.

The pack method has other options that govern the placement of widgets onscreen. We'll look at just a few.

The fill option specifies whether a widget fills its allocation rectangle (in the horizontal and/or vertical directions). Possible values are x, y, both, and none (the default being none).

The anchor option will anchor the widget inside its allocation rectangle using a "compass point" notation; the default is center, and the other possible values are n, s, e, w, ne, nw, se, and sw.

The in option will pack the widget with respect to some container other than its parent. The default, of course, is the parent.

The before and after options can be used to change the packing order of the widgets in any way desired. This is useful because widgets may not be created in any particular order as compared to their locations onscreen.

All in all, Tk is fairly flexible about placing widgets onscreen. Search the documentation and try things out.

12.1.3. Working with Buttons

One of the most common widgets in any GUI is the pushbutton (or simply button). As you would expect, the TkButton class enables the use of buttons in Ruby/Tk applications.

In any nontrivial application, we usually create frames to contain the various widgets we'll be placing onscreen. Button widgets can be placed in these containers.

A button will ordinarily have at least three attributes set:

  • The text of the button

  • The command associated with the button (to be executed when it is pressed)

  • The packing of the button within its container

Here is a little example of a button:

btn_OK = TkButton.new do   text "OK"   command (proc { puts "The user says OK." })   pack("side" => "left") end


Here we create a new button and assign the new object to the btn_OK variable; we pass in a block to the constructor, although we could use a hash if we chose. In this case, we use the multiline form (which we prefer), though in practice you can cram as much code onto a single line as you want. Recall, by the way, that the block is executed using instance_eval, so that it is evaluated in the context of the object (in this case, the new TkButton object).

The text specified as a parameter to the text method will simply be placed on the button. It can be multiple words or even multiple lines.

The pack method we have already seen. It is nothing interesting, though it is essential if the widget is going to be visible at all.

The interesting part here is the command method, which takes a Proc object and associates it with the button. Frequently, as we do here, we will use the Kernel method lambdaproc, which will convert a block to a Proc object.

The action we're performing here is rather silly. When the user presses the button, a (nongraphical) puts will be done; the output will go to the command-line window from which the program was started or perhaps an auxiliary console window.

We now offer a better example. Listing 12.1 is a fake thermostat application that will increment and decrement the displayed temperature (giving us at least the illusion that we are controlling the heating or cooling and making ourselves more comfortable). An explanation follows the code.

Listing 12.1. A Simulated Thermostat

require 'tk' # Common packing options... Top  = { 'side' => 'top', 'padx'=>5, 'pady'=>5 } Left = { 'side' => 'left', 'padx'=>5, 'pady'=>5 } Bottom = { 'side' => 'bottom', 'padx'=>5, 'pady'=>5 } temp = 74   # Starting temperature... root = TkRoot.new { title "Thermostat" } top = TkFrame.new(root) { background "#606060" } bottom = TkFrame.new(root) tlab = TkLabel.new(top) do   text temp.to_s   font "{Arial} 54 {bold}"   foreground "green"   background "#606060"   pack Left end TkLabel.new(top) do         # the "degree" symbol   text "o"   font "{Arial} 14 {bold}"   foreground "green"   background "#606060"   # Add anchor-north to the hash (make a superscript)   pack Left.update({ 'anchor' => 'n' }) end TkButton.new(bottom) do   text " Up "   command proc { tlab.configure("text"=>(temp+=1).to_s) }   pack Left end TkButton.new(bottom) do   text "Down"   command proc { tlab.configure("text"=>(temp-=1).to_s) }   pack Left end top.pack Top bottom.pack Bottom Tk.mainloop

We create two frames here. The upper one holds only a display. We display the temperature in Fahrenheit in a large font for realism (using a small, strategically placed letter "o" for a degree mark). The bottom frame holds the "up" and "down" buttons.

Notice that we are using some new attributes for the TkLabel object. The font method specifies the typeface and size of the text in the label. The string value is platform-dependent; the one shown here is valid on a Windows system. On a UNIX system, it would typically be a full X-style font name, long and unwieldy, something like -Adobe-Helvetica-Bold-R-Normal*-120-*-*-*-*-*-*.

The foreground method sets the color of the text itself. Here we pass in the string "green" (which has a predefined meaning in the internals of Tk). If you wonder whether a color is predefined in Tk, an easy way to find out is simply to try it.

Likewise, the background sets the color of the background against which the text appears. In this case, we pass it a different kind of string as a parameter, a color in typical red-green-blue hex format as you would see in HTML or in various other situations. (The string "#606060" represents a nice gray color.)

Notice that we haven't added any kind of "exit" button here (to avoid cluttering a nice simple design). As always, you can close the app by clicking the Close icon at the upper right of the window frame.

Note that the configure method is used in the commands for the buttons; this changes the text of the top label as it increments or decrements the current temperature. As we mentioned earlier, basically any attribute can be changed at runtime in this way, and the change will be reflected onscreen immediately.

We'll mention two other tricks that you can do with text buttons. The justify method will accept a parameter ("left", "right", or "center" to specify how the text will be placed on the button ("center" is the default). We already mentioned that multiple lines could be displayed; the wraplength method will specify the column at which word wrapping should occur.

The button's style may be changed with the relief method, giving it a slight three-dimensional appearance. The parameter to this method must be one of these strings: "flat", "groove", "raised", "ridge" (the default), "sunken", or "solid". The width and height methods will control the size of the button explicitly, and methods such as borderwidth also are available. For other options (which are numerous), consult a reference.

Let's look at an additional example of using a button. This new button will have an image on it rather than just text.

I created a pair of GIF images of an upward-pointing arrow and a downward-pointing one (up.gif and down.gif). We can use the TkPhotoImage class to get references to each of these. Then we can use these references when we instantiate the buttons.

up_img = TkPhotoImage.new("file"=>"up.gif") down_img = TkPhotoImage.new("file"=>"down.gif") TkButton.new(bottom) do   image up_img   command proc { tlab.configure("text"=>(temp+=1).to_s) }   pack Left end TkButton.new(bottom) do   image down_img   command proc { tlab.configure("text"=>(temp-=1).to_s) }   pack Left end


This button code simply replaces the corresponding lines in our first thermostat example. Except for the appearance of the buttons, the behavior is the same. Figure 12.2 shows the thermostat application.

Figure 12.2. Thermostat simulation (with graphical buttons)


12.1.4. Working with Text Fields

A text entry field can be displayed and manipulated using the TkEntry widget. As you would expect, numerous options are available for governing the size, color, and behavior of this widget; we will offer one sizable example that illustrates a few of these.

An entry field is only useful if there is some way to retrieve the value typed into it. Typically the field will be bound to a variable (actually a TkVariable as we'll see), though the get method can also be used.

For our code fragment, let's assume that we're writing a telnet client that will accept four pieces of information: the host machine, the port number (defaulting to 23), the user ID, and the password. We'll add a couple of buttons just for looks, for the "sign on" and "cancel" operations.

As we've written it, this code fragment also does some little tricks with frames to make things line up and look better. It's not written in a truly portable way, and a real Tk guru would disdain this approach. But just for your information, we've documented this "quick and dirty" approach to screen layout.

The screenshot is shown in Figure 12.3, and the code in Listing 12.2.

Figure 12.3. A simulated telnet client.


Listing 12.2. A Simulated Telnet Client

require "tk" def packing(padx, pady, side=:left, anchor=:n)   { "padx" => padx, "pady" => pady,     "side" => side.to_s, "anchor" => anchor.to_s  } end root = TkRoot.new() { title "Telnet session" } top = TkFrame.new(root) fr1   = TkFrame.new(top) fr1a  = TkFrame.new(fr1) fr1b  = TkFrame.new(fr1) fr2   = TkFrame.new(top) fr3   = TkFrame.new(top) fr4   = TkFrame.new(top) LabelPack  = packing(5, 5, :top, :w) EntryPack  = packing(5, 2, :top) ButtonPack = packing(15, 5, :left, :center) FramePack   = packing(2, 2, :top) Frame1Pack  = packing(2, 2, :left) var_host = TkVariable.new var_port = TkVariable.new var_user = TkVariable.new var_pass = TkVariable.new lab_host = TkLabel.new(fr1a) do   text "Host name"   pack LabelPack end ent_host = TkEntry.new(fr1a) do   textvariable var_host   font "{Arial} 10"   pack EntryPack end lab_port = TkLabel.new(fr1b) do   text "Port"   pack LabelPack end ent_port = TkEntry.new(fr1b) do   width 4   textvariable var_port   font "{Arial} 10"   pack EntryPack end lab_user = TkLabel.new(fr2) do   text "User name"   pack LabelPack end ent_user = TkEntry.new(fr2) do   width 21   font "{Arial} 12"   textvariable var_user   pack EntryPack end lab_pass = TkLabel.new(fr3) do   text "Password"   pack LabelPack end ent_pass = TkEntry.new(fr3) do   width 21   show "*"   textvariable var_pass   font "{Arial} 12"   pack EntryPack end btn_signon = TkButton.new(fr4) do   text "Sign on"   command proc {}         # Does nothing!   pack ButtonPack end btn_cancel = TkButton.new(fr4) do   text "Cancel"   command proc { exit }   # Just exits   pack ButtonPack end top.pack FramePack fr1.pack FramePack fr2.pack FramePack fr3.pack FramePack fr4.pack FramePack fr1a.pack Frame1Pack fr1b.pack Frame1Pack var_host.value = "addison-wesley.com" var_user.value = "debra" var_port.value = 23 ent_pass.focus foo = ent_user.font Tk.mainloop

Let's get the layout issues out of the way. Note that we begin by creating some frames that will stack vertically from top to bottom. The topmost frame will have two smaller ones inside it, placed onscreen from left to right.

Listing 12.2 also has a method called packing, which exists only to make the code a tiny bit cleaner. It returns a hash with the specified values set for the padx, pady, side, and anchor options.

We use the TkVariable objects just to associate the entry fields with variables. A TkVariable has a value accessor that will allow these values to be set and retrieved.

When we create a TkEntry such as ent_host, we use the textvariable option to associate it with its corresponding TkVariable object. In some cases, we use width to set the horizontal width of the field; if it is omitted, a reasonable default will be picked, usually based on the width of the current value stored in the field. Often it's acceptable to pick these widths by trial and error.

Fonts work for entry fields as they do for labels; so do colors, which aren't addressed in this example. If a font is proportional, two fields that are given the same width may not appear equal-sized onscreen.

As always, pack must be called. We've simplified these calls a little with constants.

The password field has a call to the show method because it is the one field whose value is kept secret from people reading over our shoulders. The character specified as a parameter to show (here an asterisk) will be displayed in place of each of the user's keystrokes.

As I said, the buttons are there for show. The Sign on button does nothing at all; the Cancel button does exit the program, however.

There are other options for manipulating entry fields. We can change the value under program control rather than having the user change it; we can specify the font and the foreground/background colors; we can change the characteristics of the insertion cursor and move it where we want; and much more. For all the details, consult a reference.

Because the topic is entering text, it's appropriate to mention the related Text widget. It is related to the entry widget rather in the way a two-seater plane is related to the space shuttle. It is specifically designed to handle large pieces of multiline text and in effect forms the basis for a full-fledged editor. It's not covered here because of its complexity.

12.1.5. Working with Other Widgets

Many other widgets are available for Tk. We'll mention a few here.

A check box is commonly used for a toggled value, a simple true/false or on/off field. The Tk terminology is check button, and TkCheckButton is the class name for the widget.

The example shown in Listing 12.3 is a completely bare-bones code fragment because it does not even have any buttons. It displays check boxes for three areas in which you might take coursework (computer science, music, and literature). It prints a message to the console when you select (or deselect) one of these.

Listing 12.3. Tk Check Boxes

require 'tk' root = TkRoot.new { title "Checkbutton demo" } top = TkFrame.new(root) PackOpts  = { "side" => "top", "anchor" => "w" } cb1var = TkVariable.new cb2var = TkVariable.new cb3var = TkVariable.new cb1 = TkCheckButton.new(top) do   variable cb1var   text "Computer science"   command { puts "Button 1 = #{cb1var.value}" }   pack PackOpts end cb2 = TkCheckButton.new(top) do   variable cb2var   text "Music"   command { puts "Button 2 = #{cb2var.value}" }   pack PackOpts end cb3 = TkCheckButton.new(top) do   variable cb3var   text "Literature"   command { puts "Button 3 = #{cb3var.value}" }   pack PackOpts end top.pack PackOpts Tk.mainloop

Note that the variable associated with a check box receives the value 1 when the box is selected and 0 when it is deselected. These default values can be changed with the onvalue and offvalue methods. Furthermore, the variable can be set prior to the creation of the check box to establish its initial on/off status.

If for some reason we want a check box to be grayed out, we can use the state method to set its state to disabled. The other states are active and normal; the latter is the default.

Let's alter the example in Listing 12.3. Suppose we are representing not just areas of potential but actual university majors. Ignoring double majors, it's not appropriate for more than one option to be selected at a time. In this case, of course, we need radio buttons (implemented by the TkRadioButton class).

The example in Listing 12.4 is nearly the same as the example in Listing 12.3. Obviously the class name is different. Another critical difference is that the radio buttons all share the same variable. In fact, this is how Tk knows that these buttons all belong to the same group. It is possible to have more than one group of radio buttons, but each group must share one variable among its buttons.

Listing 12.4. Tk Radio Buttons

require 'tk' root = TkRoot.new() { title "Radiobutton demo" } top = TkFrame.new(root) PackOpts = { "side" => "top", "anchor" => "w" } major = TkVariable.new b1 = TkRadioButton.new(top) do   variable major   text "Computer science"   value 1   command { puts "Major = #{major.value}" }   pack PackOpts end b2 = TkRadioButton.new(top) do   variable major   text "Music"   value 2   command { puts "Major = #{major.value}" }   pack PackOpts end b3 = TkRadioButton.new(top) do   variable major   text "Literature"   value 3   command { puts "Major = #{major.value}" }   pack PackOpts end top.pack PackOpts Tk.mainloop

The value method is used here to associate a specific value with each of the buttons. It's important to realize that any values can be used here (strings, for example). We didn't use strings simply because we wanted to emphasize that there is no direct relationship between the text of the widget and the value that is returned.

Numerous options are available to customize the appearance and behavior of both check boxes and radio button groups. The image method, for example, allows you to display an image rather than a text string. Most of the usual options for formatting and displaying widgets also apply; consult a reference for complete details.

If this book (or even this chapter) were fully devoted to Tk, we would have more to say. But it's not possible to cover these in detail; they are mentioned only to make you aware of their existence.

The list box (TkListBox) widget allows you to specify a list of values in a pull-down format so that the user can select from these. The selection mode (governed by the selectmode method) makes it possible to select these in single, extended, or browse modes. The first two modes simply determine whether the user can select only one or more than one item at a time. Browse mode is like single mode except that the selection can be moved around as the mouse button is held down. List boxes can be made fully scrollable and can hold an arbitrary number of items.

Tk has advanced menuing capabilities, including pull-down menus, tear-off menus, cascade submenus, keyboard shortcut facilities, radio button menu items, and much more. Investigate the classes TkMenu, TkMenubar, and TkMenuButton.

Perhaps the sexiest of the widgets is TkCanvas, which enables the programmer to manipulate images more or less at the pixel level. It has facilities for drawing lines and shapes, manipulating colors, and loading images in various graphics formats. If your application involves advanced graphics or user-controlled drawing, this widget will be of interest to you.

The scrollbar widget handles customized scrolling, both horizontal and vertical (for example, synchronized scrolling of two separate windows). The scale widget is basically a slider that represents a numeric value; it can be placed horizontally or vertically and can be used as input or output. For any others, consult advanced documentation.

12.1.6. Other Notes

The future of Tk is uncertain (as is true of any software system), but it is not going away in the immediate future. Ruby/Tk is based on Tk 8.4 at the time of this writing, though it will likely be updated in the future.

I should also say a few words about operating systems. In theory, Tk is platform-independent, and the practice is close to the theory. Some users, however, have reported that the Windows version is not as stable as the UNIX version. For the record, all the Tk examples in this chapter have been tested on Windows platforms and are known to work as expected.




The Ruby Way(c) Solutions and Techniques in Ruby Programming
The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming (2nd Edition)
ISBN: 0672328844
EAN: 2147483647
Year: 2004
Pages: 269
Authors: Hal Fulton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net