I had a hard time getting to speed in Ruby-Tk and most you find is was told me by the newsgroup, the Japanese Ruby-TK FAQ (link) and a few good websites. Special thanks to: Kero van Gelder, Martin Weber, Hidetoshi NAGAI, Mike Hall, ..., (who did I forget to list?)
require 'tk'
root=TkRoot.new{title "Bug or Feature or Misunderstanding?"}
canvas=TkCanvas.new(root)
canvas.pack
canvas.bind('1', proc{|e| p "#{e.x}, #{e.y}";
TkcRectangle.new(canvas, e.x,e.y,e.x-5, e.y-5)})
root.bind('Any-Key-d', proc{|e| p "#{e.x}, #{e.y}";
TkcRectangle.new(canvas, e.x,e.y,e.x-5, e.y-5)})
Tk.mainloop
require 'tk'
root=TkRoot.new{title "Le Gros SA"}
canvas = TkCanvas.new(root)
menuline=TkFrame.new(root)
menuline.pack("side"=>"top")
TkMenubutton.new(menuline) { |mb|
text "Zoom"
underline 0
menu TkMenu.new(mb) {
add 'command', 'label' => 'Zoom in',
'underline' => 0,
'command' => proc { p "in"; }
add 'command', 'label' => 'Zoom out',
'underline' => 0,
'command' => proc { p "out" }
}
pack('side' => 'top', 'padx' => '1m')
}
canvas.pack
canvas.focus
root.bind('Any-Key-s', proc{|e| TkcRectangle.new(canvas, e.x,e.y,e.x-5, e.y-5)})
Tk.mainloop
require "tk"
min_x = 0
min_y = 0
c=TkCanvas.new
TkcRectangle.new(c, 10, 10, 50, 50)
c.pack
ps = c.postscript('x' => min_x, 'y' => min_y,
'width' => c.cget('width'),'height' => c.cget('height'))
f = open('file.ps', 'w')
f.print ps
f.close
Tk.mainloop
If you have problems with c.cget, use:
TkWinfo.height c and TkWinfo.width c.
If you know of a way to convert it directly
into gif/png/jpg/whatever without using an
external conversion utility, please tell me.
canvas.pack('fill' => 'both', 'expand'=>true)
require 'tk' class D
def initialize
TkRoot.new.title('Tk fun')
main_canvas = TkCanvas.new.pack
frame_canvas = TkCanvas.new
win = TkcWindow.new(main_canvas, 1, 1, 'width'=>200, 'height'=>100,
'window'=>frame_canvas, 'anchor'=>'nw')
TkcRectangle.new(frame_canvas, 5, 5, 50, 50)
TkcText.new(frame_canvas, 10, 10, 'text'=>'sample text', 'anchor'=>'nw')
TkFrame.new {|f|
TkButton.new(f, 'text'=>'move (+10,+10)',
'command'=>proc{win.move(10,10)}).pack('side'=>'left')
TkButton.new(f, 'text'=>'move (-10,-10)',
'command'=>proc{win.move(-10,-10)}).pack('side'=>'right')
pack
}
end
end D.new
Tk.mainloop
----------------------------------------------------
No need 'pack' for canvas objects.
They are put on the canvas as soon as creation.
To define a widget on a canvas window object,
please use 'window' option of TkcWindow object. The other method is to use canvas tags.
Please read the following sample script.
In the script, I give tags to canvas objects by 'tag' option.
By Canvas tags, you can make some groups of canvas objects.
By adding some tags to same canvas object,
the object can belong to some groups.
----------------------------------------------------
require 'tk' class D
def initialize(canvas, tag = TkcTag.new(canvas))
@canvas = canvas
@tag = tag
TkcOval.new(@canvas, 20, 20, 40, 40, 'tag'=>tag)
TkcRectangle.new(@canvas, 5, 5, 50, 50, 'tag'=>[@tag, 'rectangle'])
TkcRectangle.new(@canvas, 10, 10, 60, 60, 'tag'=>[@tag, 'rectangle'])
@tag_text = TkcTag.new(@canvas)
TkcText.new(@canvas, 10, 10, 'text'=>'sample text', 'anchor'=>'nw',
'tag'=>[@tag, @tag_text])
TkcText.new(@canvas, 30, 30, 'text'=>'XXXXXXX', 'anchor'=>'nw',
'tag'=>[@tag, @tag_text])
end def move(dx, dy)
@tag.move(dx, dy)
end def move_text(dx, dy)
@tag_text.move(dx, dy) # or @canvas.move(@tag_text, dx, dy)
end def move_rectangle(dx, dy)
@canvas.move('rectangle', dx, dy)
end
end TkRoot.new.title('Tk fun')
canvas = TkCanvas.new.pack
#ctag = TkcTag.new(canvas)
#objs = D.new(canvas, ctag)
objs = D.new(canvas)
TkFrame.new {|f|
TkButton.new(f, 'text'=>'move (+10,+10)',
'command'=>proc{objs.move(10,10)}).pack('side'=>'left')
TkButton.new(f, 'text'=>'move (-10,-10)',
'command'=>proc{objs.move(-10,-10)}).pack('side'=>'right')
pack
}
TkFrame.new {|f|
TkButton.new(f, 'text'=>'move rectangle (+10,+10)',
'command'=>proc{objs.move_rectangle(10,10)}) {
pack('side'=>'left')
}
TkButton.new(f, 'text'=>'move rectangle (-10,-10)',
'command'=>proc{objs.move_rectangle(-10,-10)}) {
pack('side'=>'right')
}
pack
}
TkFrame.new {|f|
TkButton.new(f, 'text'=>'move text (+10,+10)',
'command'=>proc{objs.move_text(10,10)}).pack('side'=>'left')
TkButton.new(f, 'text'=>'move text (-10,-10)',
'command'=>proc{objs.move_text(-10,-10)}).pack('side'=>'right')
pack
} Tk.mainloop
require 'tk'
root=TkRoot.new{title "Ex1"}
canvas = TkCanvas.new(root)
canvas.pack
image = TkPhotoImage.new('file' => 'up16.gif')
t=TkcImage.new(canvas, 100, 100,
'image' => image)
Tk.mainloop
p TkWinfo.geometry Tk.root;
if used on other widgets and the result is not uptodate
call update first.
from Hidetoshi NAGAI:
I'm sorry but 'fileevent' command is not implemented on Ruby/Tk.
Because, Ruby has better way to treat streams than Tcl/Tk.
I recommend you to use threads for treatment of streams.
But attention!
Sometimes Ruby/Tk is running too slow, if the event loop, Tk.mainloop, is running in the sub thread. Please use sub threads for stream access, and use the main thread for Tk.mainloop.
from Rik Hemsley
if you're using SuSE Linux 7.3, Ruby doesn't come with Tk support
by default. I suppose this was to remove a dependency. You would have to
compile Ruby yourself, after installing the Tk devel package and
dependencies.
If you want to do binding for keyboard events, you should set focus on the target widget. Therefore, 1st answer for the problem is, "set forcus on the canvas, and bind to the canvas for keyboard events."
canvas.focus
canvas.bind('d', proc{|x,y| TkcRectangle.new(canvas, x,y,x-5,y-5)},
'%x %y')
f you don't want to set focus on the canvas widget for some reason, one of the ansers was shown by some people. And, I'll show the other.
The problem is that size of the canvas widget doesn't match to size of the root widget which gets keyboard events. Then, the answer is to make those sizes equal. It needs a little tricky way. That is to use two geometry managers, 'pack' and 'place', on the root widget. I'll show two variations for your 2nd example.
require 'tk'
root = TkRoot.new
root.geometry '400x300'
canvas = TkCanvas.new.place('relwidth'=>1.0, 'relheight'=>1.0)
menuline = TkFrame.new(root, 'relief'=>'raised', 'borderwidth'=>2) \
.pack('side'=>'top', 'fill'=>'x')
TkMenubutton.new(menuline) { |mb|
text "Zoom"
underline 0
menu TkMenu.new(mb) {
add 'command', 'label' => 'Zoom in',
'underline' => 5,
'command' => proc { p "in"; }
add 'command', 'label' => 'Zoom out',
'underline' => 5,
'command' => proc { p "out" }
}
pack('side' => 'left', 'padx' => '1m')
}
root.bind('d', proc{|x,y| TkcRectangle.new(canvas, x,y,x-5,y-5)}, '%x %y')
f = TkFrame.new(root, 'relief'=>'raised', 'borderwidth'=>2) \
.pack('side'=>'bottom', 'fill'=>'x')
TkButton.new(f, 'text'=>'EXIT', 'command'=>proc{exit}).pack('side'=>'right')
Tk.mainloop
---------------------------------------
---------------------------------------
require 'tk'
root = TkRoot.new
canvas = TkCanvas.new.pack('fill'=>'both', 'expand'=>'true')
menuline = TkFrame.new(root, 'relief'=>'raised', 'borderwidth'=>2) \
.place('anchor'=>'nw', 'relx'=>0.0, 'rely'=>0.0,
'relwidth'=>1.0)
TkMenubutton.new(menuline) { |mb|
text "Zoom"
underline 0
menu TkMenu.new(mb) {
add 'command', 'label' => 'Zoom in',
'underline' => 5,
'command' => proc { p "in"; }
add 'command', 'label' => 'Zoom out',
'underline' => 5,
'command' => proc { p "out" }
}
pack('side' => 'left', 'padx' => '1m')
}
root.bind('d', proc{|x,y| TkcRectangle.new(canvas, x,y,x-5,y-5)}, '%x %y')
f = TkFrame.new(root, 'relief'=>'raised', 'borderwidth'=>2) \
.place('anchor'=>'sw', 'relx'=>0.0, 'rely'=>1.0,
'relwidth'=>1.0)
TkButton.new(f, 'text'=>'EXIT', 'command'=>proc{exit}).pack('side'=>'right')
Tk.mainloop
Answer by Mathieu Bouchard:
Trying to figure out how to do this *without*
using nested frames.
What I want: A window with (conceptually)
"three rows":
1. label + entry field
2. label + entry field
3. three buttons
Now, I'm trying to figure out what combination
of side/anchor/fill/expand (for these seven
pack operations) will give me this.
That's because you don't understand what the pack() layout-manager allows.
it allows two columns of widgets on top of each other, one from the top to
the middle and one from the bottom to the middle, and a row of widgets on
each side of that, from outer to inner.
Therefore, you can only do this using 4 packs, or 1 grid, or 1 grid and 1
pack, depending on your aesthetics.
Here's an example using 1 grid and 1 pack.
require 'tk'
root=TkRoot.new
2.times {|y| TkLabel.new(root) {text "hello"}.grid "column"=>0,"row"=>y }
2.times {|y| TkEntry.new(root) {width 20 }.grid "column"=>1,"row"=>y }
TkFrame.new(root) {
3.times {|x| TkButton.new(self) {text x}.pack "side"=>"left" }
}.grid "column"=>0, "row"=>2, "columnspan"=>2
Tk.mainloop
Ichimunki solved this puzzel. His mail on ruby-talk.
ed_menu = MakeMenu.editor_bind_menu( ed )
ed.bind('ButtonPress-3',
proc{ |x,y| ed_menu.popup( x, y ) },
"%X %Y"
)
I'm experiencing some rather significant performance problems with IO.popen
when embedding the logic in a Tk app.
MENON Jean-Francois suggested this solution on the ruby-talk mailinglist.
The main problem, I suppose, is that threads are implemented in the ruby
interpreter while Tk.mainloop
is running in the binary library tcltk.
the solution I found is to run Tk.mainloop, and then call the reads from
inside the Tk.mainloop using Tk.After:
inp=Thread.new(){
timer=TkAfter.new(100,-1,proc {$igsClient.getlines})
timer.start
}
(100ms, -1 == loop)
require 'tk'
TkRoot.new.protocol('WM_DELETE_WINDOW', proc{p "Ouch!!\n"})
Tk.mainloop
|I'm having some problems with the following code. When I click on either
|of the buttons on the scrollbar or move the thumb within the scrollbar
|the program crashes out on me. The error message that follows is from
|Windows 1.6.5-2 but the same error turns up on two seperate Linux
|installs. As the code is mostly cut and paste from the Pickaxe book I am
|somewhat baffled. BTW the Pickaxe code works fine but then it is not in
|a class.
|
|Any pointers?
Since Ruby/Tk's "self swap" hack, you can't access instance variables
from the block to the "new" method.
| scroll = TkScrollbar.new(root) {
| command proc { |*args| @text.yview *args}
| pack('side' => 'right', 'fill' => 'y')
| }
"@text" in the block is referring the instance variable of the new
TkScrollbar. You can avoid this by
! text = @text = TkText.new(root) {
| width 60
| height 30
| }
|
| scroll = TkScrollbar.new(root) {
! command proc { |*args| text.yview *args}
| pack('side' => 'right', 'fill' => 'y')
| }
Here's a snippet from one of mine that works:
# Keyboard and Button Bindings
@doc.bind('Key-Tab') {|e| keyTab(e)}
@doc.bind('Key-Return') {|e| keyTab(e)}
#@doc.bind('Shift-Key-Tab') {|e| keyLeftTab(e)} #Windows
@doc.bind('Key-ISO_Left_Tab') {|e| keyLeftTab(e)} #Linux
@doc.bind('Key-Up') {|e| keyUpArrow(e)}
@doc.bind('Key-Down') {|e| keyDownArrow(e)}
@doc.bind('B1-ButtonRelease') {|e| Button1Release(e)}
@doc.bind('Control-Key-c') {|e| clearCell(e)}
@doc.bind('Control-Key-C') {|e| clearCell(e)}
@doc.bind('Control-Key-U') {|e| undoCell(e)}
@doc.bind('Control-Key-u') {|e| undoCell(e)}
@doc.bind('Key') {|e| anyKeyPress(e)}
Thanks to Mat Gushee for the answer.
require "tkfont"
TkFont.families.each{|f| p f}
Thanks to kryptik for the answer.
filemenu.add('command', 'label'=>"Blah", 'command'=>proc {
toplevelwindow = TkToplevel.new(root) {
title "New window"
geometry("50x50+100+100")
configure('bg'=>"black")
}
toplevelbutton = TkButton.new(toplevelwindow) {
text "Close"
command proc { toplevelwindow.destroy }
}.place('x'=>10, 'y'=>10)
}
Articles/slides/tutorials.
Ruby slides.
Back to Approximity.