RubyTk

 
   

Ruby Way
By Hal Fulton
Slots : 1.0
Table of Contents
 


Ruby/Tk

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

If Ruby had a "native" GUI, Tk would probably be it. It is the most mature of the GUI bindings at the time of this writing, and Ruby download versions are available that include Tk in a more or less turnkey fashion.

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

Overview

At the time of this writing, Tk is probably the most common GUI in use with Ruby. It was the first one made available, and it is part of the standard Ruby installation.

Some say that Tk is showing its age; for those who like clean, object-oriented interfaces, it may be something of a disappointment. However, it has the advantages of being well known, very portable, and very stable (at least insofar as the interface to Ruby is 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). Therefore, 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.

A Trivial Windowed Application

In this section we'll demonstrate the simplest possible applicationa calendar app that displays the current date.

Because it's 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 

Here, 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 6.1 shows what this looks like.

Figure 6.1. A trivial Tk application.

graphics/06fig01.gif


As 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 because 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 because 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 anchors 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.

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 in order 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 clicked)

  • The packing of the button within its container

Here is a little example of a button:

 

 btnOK = 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 btnOK variable; we pass in a block to the constructor, although we could use a hash instead. In this case, we use the "multiline" form (which we prefer), although 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 you have already seen. It is nothing interesting, although 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 proc, which will convert a block to a Proc object.

The action we're performing here is rather silly. When the user clicks 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. This 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). The code for this application is shown in Listing 6.1, and an explanation follows.

Listing 6.1 A Thermostat Simulation in Tk
 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. We use a small, strategically placed letter "o" for a degree mark.

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 ugly, 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're wondering whether a color is predefined in Tk, an easy way to find out is simply to try it.

Likewise, the background method 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.)

You'll notice 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 corner of the window frame.

You might wonder why we've used these ugly global variables in this program. Chiefly, we've done it to simplify the example. For instance, the variables $tlab and $temp have to be used inside the blocks passed to various constructors; this implies they can't be local variables.

Also 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 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 little 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 other methods, such as borderwidth, are also available. For other options (which are numerous), consult a reference.

Figure 6.2 provides an additional example of a button with an image on it.

Figure 6.2. Tk thermostat simulation (with graphical buttons).

graphics/06fig02.gif


We created a pair of GIF images of an upward-pointing arrow and a downward-pointing one. We can use the TkPhotoImage class to get references to each of these. Then we can use these references when we instantiate the buttons, as shown here:

 

 upImg = TkPhotoImage.new("file"=>"up.gif") downImg = TkPhotoImage.new("file"=>"down.gif") TkButton.new(bottom) do   image upImg   command proc { $tlab.configure("text"=>($temp+=1).to_s) }   pack $left end TkButton.new(bottom) do   image downImg   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.

Working with Text Fields

A text entry field can be displayed and manipulated using the TkEntry widget. As you would expect, there are numerous options governing the size, color, and behavior of this widget; we will offer one sizeable 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 you'll see), although the get method can also be used.

For our code fragment, let's assume that we're writing a Telnet client that accepts 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. Figure 6.3 shows what this client looks like.

Figure 6.3. The simulated Telnet client.

graphics/06fig03.gif


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. However, just for your information, we've documented this "quick and dirty" approach to screen layout. The code for this client is provided in Listing 6.2.

Listing 6.2 Tk Telnet
 require "tk" def packing(padx, pady, side, anchor=nil)   side = :left if not side   anchor = :n if anchor == nil   {  "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) $varHost = TkVariable.new $varPort = TkVariable.new $varUser = TkVariable.new $varPass = TkVariable.new labHost = TkLabel.new(fr1a) do   text "Host name"   pack $labelPack end entHost = TkEntry.new(fr1a) do   textvariable $varHost   font "{ Arial}  10"   pack $entryPack end labPort = TkLabel.new(fr1b) do   text "Port"   pack $labelPack end entPort = TkEntry.new(fr1b) do width 4   textvariable $varPort   font "{ Arial}  10"   pack $entryPack end labUser = TkLabel.new(fr2) do   text "User name"   pack $labelPack end entUser = TkEntry.new(fr2) do   width 21   font "{ Arial}  12"   textvariable $varUser   pack $entryPack end labPass = TkLabel.new(fr3) do   text "Password"   pack $labelPack end entPass = TkEntry.new(fr3) do   width 21   show "*"   textvariable $varPass   font "{ Arial}  12"   pack $entryPack end btnSignon = TkButton.new(fr4) do   text "Sign on"   command proc { }          # Does nothing!   pack $buttonPack end btnCancel = 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 $varHost.value = "samspublishing.com" $varUser.value = "william" $varPort.value = 23 entPass.focus foo = entUser.font Tk.mainloop 

Let's get the layout issues out of the way. First of all, 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.

We have also created a method called packing that 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 entHost, 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. We confess to picking these widths by trial and error.

Fonts work for entry fields as they do for labels (as do colors, which we don't play with in this example). If a font is proportional, then 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 our global variables.

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 (in this case, an asterisk) will be displayed in place of each of the user's keystrokes.

As we said, the buttons are used mainly for show. The "sign on" button does nothing at all; the "cancel" button does exit the program, however.

Other options exist for manipulating entry fields. For example, we can change the value under program control rather than having the user change it, specify the font and the foreground/background colors, change the characteristics of the insertion cursor and move it where we wish, 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 in the way the space shuttle is related to a two-seater plane. It is specifically designed to handle large pieces of multiline text and, in effect, forms the basis for a full-fledged editor.

We won't cover it here because of its complexity. However, you can again consult a reference to find out about the numerous features of this widget.

Working with Other Widgets

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

A checkbox is commonly used for a toggled valuea simple true/false or on/off field. The Tk terminology is checkbutton, and TkCheckButton is the class name for the widget.

Listing 6.3 shows a little example. This is a completely bare-bones code fragment because it does not even have any buttons. It displays checkboxes 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 them.

Listing 6.3 Tk Checkboxes
 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 checkbox 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 checkbox to establish its initial on/off status.

If for some reason we want a checkbox 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 this little example. Suppose we are representing not just areas of potential study but actual university majors. Ignore double majors for now; 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 we show in Listing 6.4 is nearly the same as the previous one. 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 6.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 value 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 checkboxes 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 here; 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 it all. The remaining widgets are not discussed here in detail; we mention them 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 them. The selection mode (governed by the selectmode method) makes it possible to select these values in single, extended, or browse mode. 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, cascading submenus, keyboard shortcut facilities, radio button menu items, and much more. For more information, 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.

Other Notes

The future of Tk is uncertain (as is true of any software system), but it is not going away in the foreseeable future. Ruby/Tk is based on Tk 8.3 at the time of this writing, although it is probably not complete and definitely has a few bugs. We're unable to predict when updates might happen.

We 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. Although this is a bit of a generalization, it is probably correct. For the record, all the examples here have been tested on Windows platforms and are known to work as expected.

Before we close, we should at least mention SpecRuby, the one existing Tk-based application builder for Ruby. This is based on the original SpecTcl (presumably pronounced spectacle, a pun that was lost as soon as it was ported from Tcl to other languages such as Perl, Java, and Ruby). While not necessarily a true RAD (rapid application development) tool, it is still useful and convenient, especially if you are developing a large or complex GUI application. SpecRuby can be found in the Ruby Application Archive.


   

 

 



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

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