|
|||
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 |
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 |
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-ButtonRelease
Control-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 |
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.
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.