|
|||
TkFrame oder
TkRoot) und packt da andere Widgets hinein, etwa Buttons oder
Labels. Wenn man dann bereit ist, die GUI zu starten, ruft man Tk.mainloop auf.
Die Tk-Engine übernimmt dann die Kontrolle über das Programm, zeigt die Widgets an und ruft als
Reaktion auf GUI-Ereignisse die entsprechenden Code-Teile auf.
require 'tk'
root = TkRoot.new { title "Ex1" }
TkLabel.new(root) {
text 'Hello, World!'
pack { padx 15 ; pady 15; side 'left' }
}
Tk.mainloop
|
Figur 15.1
Diesen Code sehen wir uns etwas genauer an. Nachdem das tk-Erweiterungs-Modul geladen ist,
erzeugen wir auf Root-Ebene ein Frame (einen Rahmen) mit TkRoot.new.
Dann machen wir ein Label-Widget als Kind des Root-Frames und setzen ein paar
Optionen für dieses Label. Schließlich packen wir den Root-Frame und geben die Kontrolle an
die Haupt-Ereignisschleife der GUI ab.
Es ist schon eine sinnvolle Angewohnheit, Root explizit anzugeben, aber man kann das
auch weglassen --- zusammen mit den Extra-Optionen --- und das Ganze auf drei
Zeilen eindampfen:
require 'tk'
TkLabel.new { text 'Hello, World!' }
Tk.mainloop
|
Tk dran. Zum Beispiel werden die Widgets Label, Button
und Entry zu den Klassen TkLabel,
TkButton und TkEntry. Man erzeugt eine Instanz eines Widgets mit
new, genau wie bei jedem anderen Objekt. Wenn man kein Elternteil für
das Widget angibt, wird es defaultmäßig an das Frame der Root-Ebene angehängt.
Normalerweise gibt man aber immer ein Elternteil an, zusammen mit vielen
anderen Optionen wie Farbe, Größe und so weiter. Wir müssen aber auch Informationen von
dem Widget zurückbekommen, während unser Programm läuft, mit Callbacks oder
mit gemeinsamen Datenbereichen.
Hash
an die Widgets weitergegeben. In Ruby kann man das genauso machen, man kann die Optionen
aber auch mit einem Code-Block übergeben; der Name der Option wird als Methoden-Name in dem
Block benutzt und die Argumente für die Option sind die Argumente für den Methodenaufruf.
Widgets brauchen ein Elternteil als erstes Argument, gefolgt von einem optionalen Hash mit
Argumenten oder dem Code-Block mit den Argumenten. Damit sind die folgenden beiden
Formen äquivalent.
TkLabel.new(parent_widget) {
text 'Hello, World!'
pack('padx' => 5,
'pady' => 5,
'side' => 'left')
}
# oder
TkLabel.new(parent_widget, text => 'Hello, World!').pack(...)
|
padx- oder pady-Optionen in diesem Beispiel)
sind in Pixeln angegeben, man kann aber auch andere Einheiten nehmen wenn man hinten ein
``c'' (Zentimeter),
``i'' (Inch), ``m'' (Millimeter) oder ``p'' (Point) anhängt.
command-Option (siehe den TkButton-Aufruf in dem folgenden Beispiel) nimmt ein Proc-Objekt entgegen, das aufgerufen wird, wenn der Callback ausgelöst wird. Hier benutzen wir Kernel::proc um den {exit}-Block zu einem Proc zu konvertieren.
TkButton.new(bottom) {
text "Ok"
command proc { p mycheck.value; exit }
pack('side'=>'left', 'padx'=>10, 'pady'=>10)
}
|
TkVariable-Proxy benutzen. Wir zeigen das in dem folgenden Beispiel. Beachte wie der TkCheckButton eingerichtet wird: Die Dokumentation sagt, dass die variable-Option ein var reference als Arument entgegennimmt. Dazu erzeugen wir eine Tk-Variablen-Referenz mit TkVariable.new. Der Zugriff auf mycheck.value liefert den String ``0'' oder
``1'', je nachdem ob die Check-Box markeirt ist oder nicht. Man kann denselben Mechanismus für alles benutzen, das eine Var-Referenz unterstützt, wie etwa Radio-Buttons und Text-Felder.
mycheck = TkVariable.new
TkCheckButton.new(top) {
variable mycheck
pack('padx'=>5, 'pady'=>5, 'side' => 'left')
}
|
configure-Methode, die genauso
wie new einen Hash oder einen Code-Block als Eingabe erwartet.
Wir ändern unser erstes Beispiel so ab, dass es als Reaktion auf das Drücken eines Buttons den
Label-Text ändert:
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 gedrückt wird, ändert sich der Text des Labels direkt von ``Hello, World!'' in ``Goodbye, Cruel
World!''.
Man kann außerdem spezielle Options-Werte eines Widgets abfragen mit
cget:
require 'tk' |
||
b = TkButton.new { |
||
text "OK" |
||
justify 'left' |
||
border 5 |
||
} |
||
b.cget('text') |
» | "OK" |
b.cget('justify') |
» | "left" |
b.cget('border') |
» | 5 |
Figur 15.2
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: Platzierungs-Management | |||||||||||||||||
Im Beispiel-Code in diesem Kapitel gibt es Referenzen auf die Widget-Methode pack. Das ist, wie
sich herausstellt, ein sehr wichtiger Aufruf, denn lässt man ihn weg, dann sieht man vom Widget --- nichts.
pack ist ein Kommando, das dem Platzierungs-Manager erzählt, das Widget nach unseren
Vorgaben zu platzieren. Platzierungs-Manager kennen drei Kommandos:
pack ist dabei das gebräuchlichste Kommando, deshalb nehmen wir das auch für unsere Beispiele.
| |||||||||||||||||
bind-Methode des Widgets kann man ein Event eines speziellen Widgets mit einem Code-Block verknüpfen.
Wir haben zum Beispiel ein Button-Widget, das ein Bild anzeigt. Wir möchten, dass sich das Bild jedesmal ändert, wenn die Maus über dem Button ist.
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 zwei GIF-Bild-Objekte aus Dateien auf Festplatte. Als
nächstes erzeugen wir einen Button (ganz schlau ``b'' genannt), der das Bild image1 anzeigt.
Dann verknüpfen wir das ``Enter''-Event so, dass es dynamisch das Bild des Buttonns nach image2 ändert, und das ``Leave''-Event so, dass wieder das Bild image1 angezeigt wird.
Dieses Beispiel zeigt die einfachen Events ``Enter'' und ``Leave.'' Aber das an bind als Argument übergebene benamte Event kann aus mehreren Unter-Strings zusammengesetzt sein, getrennt durch Bindestriche in der Reihenfolge
modifier-modifier-type-detail. Modifikatoren (modifier) werden in der Tk-Referenz aufgelistet und sind zum Beispiel Button1, Control, Alt,
Shift und so weiter. Type ist der Name der Events (von der X11-Namens-Konvention) und umfasst Events wie
ButtonPress, KeyPress und Expose. Detail ist entweder eine Zahl von 1 bis 5 für Buttons oder ein Tastencode für Tastatureingaben. So wird zum Beispiel eine Verknüpfung, die das Loslassen der linken Maustaste bei gleichzeitig gedrückter Strg-Taste überwacht, so angegeben:
Control-Button1-ButtonReleaseControl-ButtonRelease-1
Der Event selber kann bestimmte Felder enthalten, wie etwa der Zeitpunkt des Events oder die x- und y-Position. bind kann mit Event-Feld-Codes diese Sachen an den Callback weiterreichen.
Diese Event-Feld-Codes werden wie printf-Spezifikationen benutzt. Wenn man etwa die x- und y-Positionen bei einer Mausbewegung ermitteln will, gibt man beim Aufruf von bind drei Parameter an. Der zweite Parameter ist das Proc für den Callback und der dritte ist der String für das Event-Feld.
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
|
Figur 15.3
``Wir konnten den Künstler nicht finden, also hängten wir das Bild...''
TkCanvas, TkListbox und
TkText können aber auch mit Scrollbars eingesetzt werden, so dass man auf einem Ausschnitt des ``großen Bildes'' zeichnen kann.
Die Kommunikation zwischen einem Scrollbar und einem Widget ist zweiseitig. Wenn man den Scrollbar bewegt, muss sich auch die Ansicht im Widget bewegen; und wenn die Ansicht im Widget sich aus irgendwelchen anderen Gründen ändert, muss sich auch der Scrollbar ändern, um die naue Position widerzuspiegeln.
Weil wir bis jetzt noch nicht so viel mit Listen gearbeitet haben, wird unser scrollendes Beispiel eine zu scrollende Textliste sein. Im folgenden Beispiel fangen wir an mit der Erzeugung einer guten alten TkListbox. Dann machen wir ein TkScrollbar. Der Callback des Scrollbar (gesetzt mit command) ruft die yview-Methode des Listen-Widgets auf, die dann den Wert des sichtbaren Ausschnitts der Liste in y-Richtung ändert.
Nachdem dieser Callback feststeht, gehen wir in die andere Richtung: wenn die Liste es notwendig findet, zu scrollen, dann setzen wir den passenden Bereich in dem Scrollbar mit TkScrollbar#set.
Wir werden dasselbe Fragment in einem voll funktionfähigen Programm im nächsten Abschnitt benutzen.
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 einen Block benutzte, um sicherzustellen, dass die Datei nach Benutzung auch geschlossen wurde? Wir machen dasselbe mit der Methode busy, wie wir im nächsten Beispiel zeigen.
Dieses Programm zeigt auch ein paar einfache TkListbox-Manipulationen --- Hinzufügen
von Elementen zu der Liste, das Einrichten eines Callbacks für das Loslassen des
Maus-Buttons [Man nimmt das Loslassen und nicht das Drücken, weil das Widget erst nach dem Drücken überhaupt selektiert wird.] und die Rückgabe der aktuellen Auswahl.
Bis jetzt haben wir TkPhotoImage nur zum direkten Anzeigen der Bilder benutzt,
aber man kann genauso gut zoomen, ein Subsample anfertigen oder Teile des Bildes anzeigen.
Hier werden wir die Subsample-Fähigkeit benutzen um das Bild zur Ansicht zu verkleinern.
Figur 15.4
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 und riefen danach gleich den
Garbage-Collector auf, um den Müll zu beseitigen.
TkWidget liegt und nicht in der Klassen-Instanz.
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, um eine Ruby-Variable an den Wert eines Widgets zu binden. Man kann den value-Zugriff in TkVariable
(TkVariable#value und TkVariable#value=) nehmen, um den Inhalt des Widgets direkt zu beeinflussen.