|
|||
Previous < |
Contents ^
|
Next >
|
TkFrame
or TkRoot
) and then
create the widgets that populate it, such as buttons or labels.
When you are ready to start the GUI, you invoke Tk.mainloop
. The
Tk engine then takes control of the program, displaying widgets and
calling your code in response to GUI events.
require 'tk' root = TkRoot.new { title "Ex1" } TkLabel.new(root) { text 'Hello, World!' pack { padx 15 ; pady 15; side 'left' } } Tk.mainloop |
tk
extension module, we create a
root-level frame using TkRoot.new
. We then make a label widget
as a child of the root frame, setting several options for the
label. Finally, we pack the root frame and enter the main GUI event loop.
It's a good habit to specify the root explicitly, but you could leave it
out---along with the extra options---and boil this down to a three-liner:
require 'tk' TkLabel.new { text 'Hello, World!' } Tk.mainloop |
Tk
to the front of it. For instance,
the widgets Label, Button, and Entry become the classes TkLabel
,
TkButton
, and TkEntry
. You create an instance of a widget
using new
, just as you would any other object. If you don't
specify a parent for a given widget, it will default to the root-level
frame. We usually want to specify the parent of a given widget, along
with many other options---color, size, and so on. We also need to be
able to get information back from our widgets while our program is
running by setting up callbacks and sharing data.
Hash
. You can do that in Ruby
as well, but you can also pass options using a code block; the name
of the option is used as a method name within the block and arguments to
the option appear as arguments to the method call. Widgets take a
parent as the first argument, followed by an optional hash of options
or the code block of options. Thus, the following two forms are
equivalent.
TkLabel.new(parent_widget) { text 'Hello, World!' pack('padx' => 5, 'pady' => 5, 'side' => 'left') } # or TkLabel.new(parent_widget, text => 'Hello, World!').pack(...) |
padx
and pady
options in these
examples) are assumed to be in pixels, but may be specified in
different units using one of the suffixes ``c
'' (centimeter),
``i
'' (inch), ``m
'' (millimeter), or ``p
'' (point).
command
option (shown in
the TkButton
call in the example that follows) takes a Proc
object, which will be called when the callback fires. Here we use
Kernel::proc
to convert the {exit}
block to a Proc
.
TkButton.new(bottom) { text "Ok" command proc { p mycheck.value; exit } pack('side'=>'left', 'padx'=>10, 'pady'=>10) } |
TkVariable
proxy. We show this in the following example. Notice
how the TkCheckButton
is set up; the documentation says that the
variable
option takes a var reference as an argument. For
this, we create a Tk variable reference using TkVariable.new
.
Accessing mycheck.value
will return the string ``0
'' or
``1
'' depending on whether the checkbox is checked. You can use
the same mechanism for anything that supports a var reference, such as
radio buttons and text fields.
mycheck = TkVariable.new TkCheckButton.new(top) { variable mycheck pack('padx'=>5, 'pady'=>5, 'side' => 'left') } |
configure
method, which takes a Hash
or a code block in the
same manner as new
. We can modify the first example to change
the label text in response to a button press:
lbl = TkLabel.new(top) { justify 'center' text 'Hello, World!'; pack('padx'=>5, 'pady'=>5, 'side' => 'top') } TkButton.new(top) { text "Cancel" command proc { lbl.configure('text'=>"Goodbye, Cruel World!") } pack('side'=>'right', 'padx'=>10, 'pady'=>10) } |
Cancel
button is pressed, the text in the label will
change immediately from ``Hello, World!
'' to ``Goodbye, Cruel
World!
''
You can also query widgets for particular option values using
cget
:
require 'tk'
|
||
b = TkButton.new {
|
||
text "OK"
|
||
justify 'left'
|
||
border 5
|
||
}
|
||
b.cget('text')
|
» |
"OK"
|
b.cget('justify')
|
» |
"left"
|
b.cget('border')
|
» |
5
|
require 'tk' class PigBox def pig(word) leadingCap = word =~ /^A-Z/ word.downcase! res = case word when /^aeiouy/ word+"way" when /^([^aeiouy]+)(.*)/ $2+$1+"ay" else word end leadingCap ? res.capitalize : res end def showPig @text.value = @text.value.split.collect{|w| pig(w)}.join(" ") end def initialize ph = { 'padx' => 10, 'pady' => 10 } # common options p = proc {showPig} @text = TkVariable.new root = TkRoot.new { title "Pig" } top = TkFrame.new(root) TkLabel.new(top) {text 'Enter Text:' ; pack(ph) } @entry = TkEntry.new(top, 'textvariable' => @text) @entry.pack(ph) TkButton.new(top) {text 'Pig It'; command p; pack ph} TkButton.new(top) {text 'Exit'; command {proc exit}; pack ph} top.pack('fill'=>'both', 'side' =>'top') end end PigBox.new Tk.mainloop |
Sidebar: Geometry Management | |||||||||||||||||
In the example code in this chapter,
you'll see references to the
widget method pack . That's a very important call, as it
turns out---leave it off and you'll never see the widget. pack
is a command that tells the geometry manager to place the widget
according to constraints that we specify. Geometry managers
recognize three commands:
pack is the most commonly used command, we'll use it
in our examples.
|
bind
method.
For instance, suppose we've created a button widget that displays an image.
We'd like the image to change when the user's mouse is over the
button.
image1 = TkPhotoImage.new { file "img1.gif" } image2 = TkPhotoImage.new { file "img2.gif" } b = TkButton.new(@root) { image image1 command proc { doit } } b.bind("Enter") { b.configure('image'=>image2) } b.bind("Leave") { b.configure('image'=>image1) } |
TkPhotoImage
. Next we create a button (very cleverly named
``b''), which displays the image image1
. We then bind the
``Enter'' event so that it dynamically changes the image displayed by
the button to image2
, and the ``Leave'' event to revert back to
image1
.
This example shows the simple events ``Enter'' and ``Leave.'' But the
named event given as an argument to bind
can be composed of
several substrings, separated with dashes, in the order
modifier-modifier-type-detail. Modifiers are listed in the Tk
reference and include Button1
, Control
, Alt
,
Shift
, and so on. Type is the name of the event (taken
from the X11 naming conventions) and includes events such as
ButtonPress
, KeyPress
, and Expose
. Detail is
either a number from 1 to 5 for buttons or a keysym for keyboard
input. For instance, a binding that will trigger on mouse release of
button 1 while the control key is pressed could be specified as:
Control-Button1-ButtonRelease
Control-ButtonRelease-1
The event itself can contain certain fields such as the time of the event
and the x and y positions. bind
can pass these items to the callback,
using event field codes. These are used like printf
specifications. For instance, to get the x and y coordinates on
a mouse move, you'd specify the call to bind
with three
parameters. The second parameter is the Proc
for the callback, and
the third parameter is the event field string.
canvas.bind("Motion", proc{|x, y| do_motion (x, y)}, "%x %y") |
require 'tk' class Draw def do_press(x, y) @start_x = x @start_y = y @current_line = TkcLine.new(@canvas, x, y, x, y) end def do_motion(x, y) if @current_line @current_line.coords @start_x, @start_y, x, y end end def do_release(x, y) if @current_line @current_line.coords @start_x, @start_y, x, y @current_line.fill 'black' @current_line = nil end end def initialize(parent) @canvas = TkCanvas.new(parent) @canvas.pack @start_x = @start_y = 0 @canvas.bind("1", proc{|e| do_press(e.x, e.y)}) @canvas.bind("2", proc{ puts @canvas.postscript({}) }) @canvas.bind("B1-Motion", proc{|x, y| do_motion(x, y)}, "%x %y") @canvas.bind("ButtonRelease-1", proc{|x, y| do_release (x, y)}, "%x %y") end end root = TkRoot.new{ title 'Canvas' } Draw.new(root) Tk.mainloop |
TkCanvas
, TkListbox
, and
TkText
can be set up to use scrollbars, so you can work on a
smaller subset of the ``big picture.''
Communication between a scrollbar and a widget is
bidirectional. Moving the scrollbar means that the widget's view
has to change; but when the widget's view is changed by some other
means, the scrollbar has to change as well to reflect the new
position.
Since we haven't done much with lists yet, our scrolling example will
use a scrolling list of text. In the following code fragment, we'll
start off by creating a plain old TkListbox
. Then, we'll make a
TkScrollbar
. The scrollbar's callback (set with command
)
will call the list widget's yview
method, which will change the
value of the visible portion of the list in the y-direction.
After that callback is set up, we make the inverse association: when the list
feels the need to scroll, we'll set the appropriate range in the
scrollbar using TkScrollbar#set
.
We'll use this same fragment in a fully functional program in the next
section.
list_w = TkListbox.new(frame, 'selectmode' => 'single') scroll_bar = TkScrollbar.new(frame, 'command' => proc { |*args| list_w.yview *args }) scroll_bar.pack('side' => 'left', 'fill' => 'y') list_w.yscrollcommand(proc { |first,last| scroll_bar.set(first,last) }) |
File.new
uses a block to ensure that the file is closed after it
is used? We can do a similar thing with the method busy
, as shown
in the next example.
This program also demonstrates some simple TkListbox
manipulations---adding elements to the list, setting up a callback on a
mouse button release,[You probably want the button release,
not the press, as the widget gets selected on the button press.] and
retrieving the current selection.
So far, we've used TkPhotoImage
to only display icons directly,
but you can also zoom, subsample, and show portions of images as
well. Here we use the subsample feature to scale down the image for
viewing.
require 'tk' def busy begin $root.cursor "watch" # Set a watch cursor $root.update # Make sure it updates the screen yield # Call the associated block ensure $root.cursor "" # Back to original $root.update end end $root = TkRoot.new {title 'Scroll List'} frame = TkFrame.new($root) list_w = TkListbox.new(frame, 'selectmode' => 'single') scroll_bar = TkScrollbar.new(frame, 'command' => proc { |*args| list_w.yview *args }) scroll_bar.pack('side' => 'left', 'fill' => 'y') list_w.yscrollcommand(proc { |first,last| scroll_bar.set(first,last) }) list_w.pack('side'=>'left') image_w = TkPhotoImage.new TkLabel.new(frame, 'image' => image_w).pack('side'=>'left') frame.pack list_contents = Dir["screenshots/gifs/*.gif"] list_contents.each {|x| list_w.insert('end',x) # Insert each file name into the list } list_w.bind("ButtonRelease-1") { index = list_w.curselection[0] busy { tmp_img = TkPhotoImage.new('file'=> list_contents[index]) scale = tmp_img.height / 100 scale = 1 if scale < 1 image_w.copy(tmp_img, 'subsample' => [scale,scale]) tmp_img = nil # Be sure to remove it, the GC.start # image may have been large } } Tk.mainloop |
nil
and call the garbage collector
immediately to remove the trash.
TkWidget
being used, not
your class instance.
Perl/Tk: $widget = $parent->Widget( [ option => value ] ) Ruby: widget = TkWidget.new(parent, option-hash) widget = TkWidget.new(parent) { code block } |
Perl/Tk: -background => color Ruby: 'background' => color { background color } |
Perl/Tk: -textvariable => \$variable -textvariable => varRef Ruby: ref = TkVariable.new 'textvariable' => ref { textvariable ref } |
TkVariable
to attach a Ruby variable to a widget's value.
You can then use the value
accessors in TkVariable
(TkVariable#value
and TkVariable#value=
) to
affect the contents of the widget directly.
Previous < |
Contents ^
|
Next >
|