Verwendung Standard Ruby GUI:TK
Der Standard für grafische
Öberfläche, der in Rubyprogrammen am
häufigsten erscheint, ist TK. TK wurde
speziell als GUI für die in der Mitte
achtziger entwickelte Tcl-Skriptsprache von
John Ousterhout entwickelt, seitdem aber
ist Toolkit fast auf allen gängigen
Plattformen integriert worden, genau so
kann Tk mit anderen Skriptsprachen wie Perl
und Python verwendet werden. Tk's Widgets
sind mit mehr modernenen GUI's vergleichbar
und es unterstützt sogar MAC OS.
Installation
Ein von wesentlichen Vorteilen der
Verwendung Ruby mit Tk ist es, dass Tk ein
Standard Toolkit für Entwicklung GUI's mit
Ruby ist, und es ist leicht und ohne großen
Aufwand das Programmieren anzufangen.
Ruby/Tk benötigt Tk als Selbstpaket sowie
als Ruby/Tk Erweiterungsmodul. Sie können
sich Source code für Tk bei der Tcl/Tk
Homepage www.tcltk.org besorgen
und dabei müssen Sie es dabei selbst
kompilieren, aber es gibt schon fertige
Binäres für Tk und zwar für alle gängige
OS(Linux, Windows). Um sich leichter zu
machen, empfiehlt es sich Standard Ruby
Distribution für Windows bei der "Ruby Installer for
Windows" Sobald Sie sie installieren,
haben Sie bereits eine funktionfähige Tk
Umgebung. Viele Linux Distributionen bieten
auch Tcl/Tk als Standard Option bei der
Installation des Betriebsystemes an. Die
andere Möglichkeit Tk zu installieren, was
allerdings ein wenig komplizierter ist, ist
das Erweiterungsmodule für Ruby, die in
Ruby's Source Code vorhanden sind. Wenn Sie
sich selbst Ruby kompilieren würden, werden
Ruby/Tk Erweiterungsmodule automatisch in
Ihrem System installiert. Standard Ruby
Installer für Windows also beinhaltet schon
Ruby/Tk Erweiterungsmodule.
Ruby/Tk Basis
Ruby/Tk stellt eine Anzahl von Klassen
zur Verfügung um verschiedene Tk Widgets zu
präsentieren. Dabei sind Namen von Ruby/Tk
Klassen für Widgets Tk Basis Klassen Namen
ähnlich. Zum Beispiel "Entry-Widget" von Tk
ist auch als TkEntry Klasse in
Ruby/Tk vertreten. Ein typischer Aufbau von
einem Ruby/Tk Programm ist eine Erzeugung
eines "main" oder "root" Fensters(als
Beispiel TkRoot-Klasse), dann
werden andere Widgets zu dem "root" Fenster
hinzugefügt und damit wird "user interface"
aufgebaut, und am Schluss des Codes wird
sogennante "main event loop" mit
Tk.Mainloop aufgerufen. Das
traditionelle Programm "Hello Word" für
Ruby/Tk wird so aussehen:
require 'tk'
root = Tkroot.new
button = TkButton.new(root) {
text "Hello, World!"
command proc {puts "Ich sage Hello!"
}
}
button.pack
Tk.mainloop
Nun wird am MS-DOS-Promt diese
Datei, die wir z.B. hello_world.rb nennen
würden aufgerufen. Und so könnte es
aussehen:
Nachdem Sie das Skript
aufgerufen haben, erscheint GUI:
Und wenn Sie nun die
Schalfläche drücken, sieht es so aus:
Die erste Zeile lädt einfach
Ruby/Tk Erweiterung in den Ruby Interpreter
und zweite Zeile errichtet "top-level
window"(Hauptfenster) für das Programm.
Schließlich komemen wir zum interesantesten
Teil:
button =
TkButton.new(root) {
text "Hello, World!"
command proc { puts "Ich sage
Hello!" }
}
Hier errichten wir ein
Button,dessen Eltern's Widget das
Hauptfenster ist. Genauso wie bei anderen
GUI Toolkits, werden wir sehen, dass
Ruby/Tk ein Modell verwendet, das sich auf
"Zussamentsetzung" basiert, wo die Eltern's
Widgets ein oder mehrere Kinders Widgets
enthalten, die auch als "Behälter für
andere Widgets zur Verfügunug stehen
können. Dieser Codesteil zeigt nur einen
Weg, wie man bei der Configuration von
verschiedenen Optionen vorgehen kann.
Innerhalb vom Codeblocks können Sie
Methoden aufrufen, die das Aussehen des
Erscheinens oder des Verhaltens von Widgets
ändern. In unserem Beispiel die Methode
text wurde verwendet um
den Text in Button erscheinen zu lassen,
während die
command Methode mit einer
Prozedur verbunden, des sogennanten
"Callback"(mehr darüber wird später
erklärt). Als abwechselnde aber
gleichwertige Form zum Angeben von Widgets
Optionen ist das, dass man sie
als key, value
(Hash-style Paare) verwendet werden als
zweite und weitere Argumente für die
new Funktion und das als
Folgendes aussieht:
button =
TkButton.new(root, text => "Hello,
World!",
command => proc { puts "Ich sage
Hello!"})
Die zweite Zeile ist wichtig,
weil sie dem Hauptfenster eine Anweisung
gibt, den Button am richtigen Platzt zu
setzen. Für diesen Beispiel ist das
natürlich nicht so relevant, da das
Hauptfenster nur ein Kind-Widget hat. Wie
zum Beispiel wir später sehen werden, haben
wirkliche Programme viel mehr komplizierte
Layouts, die noch mehrere Ebenen haben und
auch ihrerseits andrere Widgets beinhalten.
Für diese Fälle werden wir auch zusätliche
Argumente zur pack Methode
übergeben, um hinzuweisen, wo die Widgets
in Eltern-Widgets plaziert werden sollten,
wie die Grösse des Buttons geändert werden
könnte, wenn die Größe des Eltern's Widget
geändert wurde und auch andere mit Layout
zusamennhängende Aspekte.
Dieses Beispielprogramm endet,
wie die meisten Ruby/Tk Programme es tun,
mit dem Aufrufen Tk.mainloop;.
Aber die Methode greift eingentlich schon
am Beginn des Programmes. An dieser Stelle
befindet sich das Programm in einer
sogennanten unbegrenzten Schleife, wartet
auf Ereignisse, die vom Anwender losgelöst
werden und dann werden sie an den passenden
"Handler" abgeschickt. Viele Programme, die
mit Ruby/Tk entwickelt wurden, haben schon
eine gewisse Struktur an Ereignissen, und
dann wird der Code dafür geschrieben um
diese Ereignisse zu führen. Das ist das
Thema des follgenden Teil.
Integrieren von Ereignissen und
Aufrufen in Ruby/Tk GUI's
Tk's Ereginiss Model ist auf
zwei eng zusammenhängende Abläufe getrennt.
Auf einer Seite das Betriebsystem erzeugt
auf niedriger Stufe ein Ereigniss wie
z.Beispiel: "der Mauscursor bewegt sich
gerade in einem Fenster oder der Anwender
drückt einfach eine Taste "S" auf der
Tastatur". Auf anderer Seite ruft Tk in
Ihrem Programm ein Ereigniss um darauf
hinzuweisen, dass etwas Bedeutunsvolles in
Widget geschehen ist.(z.B. Button wurde
angeklickt). Für beide Fälle können Sie
einen Codeblock oder einen Ruby
Proc Objekt schreiben, das
genau angibt, wie das Programm auf das
Ereigniss oder den Aufruf reagiert.
Zuerst lassen Sie uns einen
Blick werfen, wie die Methode
bind verwendet wird um
betriebsystemspezifische Ereignisse mit
Ruby's Prozeduren zu verbinden, welche
dementsprechend darauf reagiren.
Die bind Methode
wird in einfachsten Form für die Einbindung
des Ereignisses und des Codeblockes
eingesetzt. Daraufhin führt Tk das
Codeblock, was als eine Antwort aufs
entspreschende Ereigniss ist. Zum Beispiel
um ein ButtonRelease Ereigniss
für den ersten Mausbutton in irgendeinem
Fenster zu packen, würden so schreiben:
someWidget.bind('ButtonRelease-1')
{
...Codeblock...
}
Für manche Ereignisstypen ist
es ausreichend, den elementaren
Ereignissnamen zu verwenden, wie z.B.
"Configure" oder "Destroy", aber für andere
brauchen wir mehr spezifischen Namen. Dabei
kann der Eventsname auch
zusätzliche modifiers
und details enthalten.
Der "Modifier" ist ein "String" wie
"Schift" "Control" oder "Alt", welches
hindeutet, was dabei gedrükt wird. Im
Detail wird eine von 1 bis 5 Nummer
ausgewählt, welche auf die Nummer von
Mausbutton hinweisen, oder kennzeichnen
eine Taste auf die Tastatur. So z.B. um ein
Event abzufangen, welches erzeugt wird,
wenn der Anwender die Ctrl
Taste festhält und dabei Rechtemausttaste
betätigt(manchmal als Button 3 genannt
wird) über das Fenster, würde man so
geschrieben:
aWindow.bind('Control-ButtonPress-3',
proc {
puts "Wauuu!!!" })
Die Namen von diesen Events
sind von den Namen entsprechenden X11
Eventstypes abgeleitet worden,
hauptsächlich aus historischen Gründen;
Tcl/Tk war ursprünglich für Unix
Betriebsystem entwickelt und sein X Window
System. Danach wurde Tk auf Windows und
Macintosh portiert und diese Portierungen
verwenden gleiche Eventsnamen für die
Darstellung ihrer "native" Ereignissen, die
auf jeweiliges System beheimatet sind. Das
Beispielprogramm, das wir später entwicklen
werden, verwendet ein paar Eventstypen,
aber wenn Sie sich für die komplette Liste
von aktuellen Eventsnamen,
modifier und Details
interessieren, sollten Sie "manual pages"
für Tk's bind command zur Rate
ziehen. Eine gute Online-Quelle für diese
Art von Referenzen ist die Tcl/Tk
Dokumentation auf "Tcl Developer Xchange
Webseite(siehe hier)
Es ist natürlich nützlich,
solche Art von "low-level" Events abfangen
zu können, aber mehr häufiger werden Sie
sich mit "high-level" Aktionen
beschäftigen. Zum Beispiel Sie hätten gern
einfach gewusst, wenn der Anwender auf den
Help Button klickt, wobei
Sie brauchen wirklich nicht zu wissen, wie
es abläuft, wennn der Anwender mit linken
Maustaste diesen Button anklickt und dann
einige Millisekunden später den loslassen
Viele Ruby/Tk Widgets können
callbacks auslösen, wenn der
Anwender sie aktiviert und Sie könnet
command callback genau
angeben, welche bestimmte Codeblocks oder
Prozeduren aufgerufen werden müssen und was
danach geschieht. Z.Beispiel mit einer
beliebigen Option können Sie festlegen
command callback Prozedure,
wenn Sie ein Widget zaubern:
helpButton =
TkButton.new(buttonFrame) {
text "Help"
command proc { showHelp }
}
oder Sie können es später die
Verwendung Widget's command
Methode festlegen:
helpButton.command proc {
showHelp }
Sie können aber auch disen Code
so schreiben, seitdem command
Methode es erlaubt:
helpButton =
TkButton.new(buttonFrame) {
text "Help"
command { showHelp }
}
Viele Widgets, wie
TkCanvas, TkListbox und TkText
können nicht verwendet werden, um z.B.
ihren Inhalt im ihnen zugewiesenen Raum zu
zeigen. Zum Beispiel, wenn Sie
TkText Widget für die
Darstellung eines langen Textes verwenden,
werden höhstens nur ein Teil von Text
sehen. Deswegen werden Sie auf Hauptwidget
für ein paar Seiten einen typischen
TkScrollbar beifügen. Damit
wird es dem Anwender eine Möglichkeit
gegeben, durch den gesamten Inhalt zu
scrollen. Um alles das richtig zu machen
und zwar das Zussammenspielen von
"Scrollbalken" und Widgets wie TkText
und TkListbox kann man also
callbacks erzeugen wenn der
Inhalt von Widgets vertikal oder gorisontal
gescrollt werden muß. Um die
callbacks mit Codeblocks zu
verbinden können Sie Widget's
xscrollcomamand or
yscrollcommand Methode
verwenden. Wir werden es uns später bei
unserem Beispielprogramm ansehen, wie das
alles funktioniert.
Arbeitsweise mit Ruby/Tk Layout
Manager
Wenn Sie Ihres Programm
etnwerfen, ist es wichtig zu verstehen, wie
Ruby/Tk Events und Callback's
funktionieren. Eebenso ein relevanter
Aspekt beim Entwurf ist die Gestaltung von
"User Interface". Viele Widgets dienen als
interaktive Komponenten, when der Anwender
darauf klickt oder tippt etwas um das
Programm zu bringen, irgendwelche Aktion
auszuführen. Andere Widgets, wie auch
immer, sind mehr "passiv" und könnten als
Behälter(Eltern) für schon anderer Widgets.
Wir haben bereits gesehen, dass "top-level"
"root" Widget ist einer von solchen Widgets
und innerhalb des Hauptfensters können Sie
TkFrame Widget verwenden. Damit werden die
Kinder Widgets zussammengruppiert.
Der Layout Manager für den
Behälter definiert im Grunde Leitfaden und
wird zum Festlegen der Position und Größe
im Hauptfenster vorhandenden Widgets Wie
Sie sehen werden ,gleich nachdem Sie
verstanden haben, wie Layout Manager
funktioniert dass es Einiges an
Experimentfreude gibt und Sie werden
Möglichkeiten haben, um Ihre geistige
Vorstellungen,was GUI's betrifft,in Code zu
verwirklichen und sie richtig zu
implementieren. Mit Ruby/Tk können Sie aus
drei verschiedenen Layout Manager
auswählen, obwohl Sie nicht brauchen
denselben Layout zu verwenden. In
Wirklichkeit für wichtigen GUI's ist es
ziemlich wahrscheinlich, dass Sie mehrere
Layots Manager verwenden werden.
Der einfachste Layout Manager
ist placer, mit dem Sie
lediglich Kinder Elementen plazieren
würden, bzw. die Position und Größe
festlegen. Auf ersten Blick könnte es
angemessen klingeln, doch viele "GUI
Builder" Tools dass sie Ihnen erlaubt
Widgets auf Toolpallete wegzuzerren, oder
fallen lassen auf dem Arbeitsfenster,
verwenden diese Methode in dem Code,
während sie den erzeuge. Nachteile diesen
unflexsiblen festen Layout Managers werden
offentsichtlich, wenn man sobald versucht,
das Programm auf anderen Computer mit
anderer Konfiguration und wahrscheinlich
auch dass da ein anderes OS ist, zum Laufen
zu bringen. Zum Beispiel, wenn Systemfonts
verschieden sind, der Button erfordert auf
Ihrem System für den Text nur 40 px Breite
auf anderen System könnte es 60 px sein.
Wenn Sie einen Textfeld fest zu diesem
Button unmittelbar rechts verankert haben,
werden jetzt die zwei Widgets sich
gegenseitig überlappen. Da es so
unflexsibel ist, werden Sie
höhstwahrscheinlich nicht diesen Layout
Manager im Praxis benutzen.
Der nächste Layout Manager ist
grid, welcher seine Kinder
Widgets in einer tabellenähnlichen Ordnung
plaziert. Wenn man Kinder Widgets zu dem
Elternfenster hinzufügt, legt man sozusagen
Spalten und Reihen fest, die jetzt mit
diesem Layout Manager für die Platzierung
von Elementen verwendet werden künenen. Die
Kinder Elemente werden so zugeordnet, dass
alle Elementen in einer oder mehreren
gleichen Spalten und Reihen mit gleichen
Breite und Höhe zu finden sind. Um den
schnellen Eindrück zu beschaffen, bietet
sich hier ein Beispiel, mit
grid Layout und mit 3 Reihen
und 4 Spalten auf "Label" Widget:
require 'tk'
root = TkRoot.new
3.times { |r|
4.times { |c|
TkLabel.new(root) {
text "row #{r},
column #{c}"
}.grid('row' =>
r, 'column' => c,
'padx'
=> 10, 'pady' => 10)
}
}
Tk.mainloop
Unten sieht man ein Ergebnis,
wenn dieses kleines Programm laufen
lässt:
Der grid Layout
Manager ist aber mehr leistungsfähig, als
man an diesem Beispeil erkennen lässt. Sie
können sehen, dass man bei
Grundeinstellungen so ist, dass
grid jedes Kind Element in
seiner Zelle zentriert, aber man kann
zusätzliche Argumente verwenden und so kann
grid Methode festsetzen, dass
ein oder mehrere Seiten vom Kind-Element
wie von einem "Kleber" ausgedehnt werden
können. Außrdem grid Zellen können mehrere
Reihen und Spalten umfassen. Um mehr
darüber eine Information zu beschaffen,
sollte man in Tk Referenz nachschauen.
Der letzte Layout Manager, den
wir besprechen werden, ist
packer. Ihn werden Sie
häufiger verwenden, weil er sehr flexsibel
sogar leichter zu benutzen ist. Der
packer verwendet als ein Model
zur Zuteilung der Position und Größe
sozusagen "Hohlraum". Man kann sich einen
leeren Raum vorstellen, bevor man die
Kinder Element hinzugefügt werden. Wenn man
danach das erste Kind-Element hinzufügt
legt man die Position der Seiten vom den
Rand des rechtwinkligen "Hohlraums"(links,
rechts, oben, unten) fest. Der
packer weist dem Kind-Element
die gesamte Seite zu und dann verringert
die Größe der Seite um die Menge, die das
erste Kind-Element beansprucht hat. Das ist
wichtig zu merken, dass der
packer die ganze Seite des
Raums für den neuen dazugekommenen
Kind-Element zuteilt, selbst wenn das
Kind-Element nicht braucht. Die
aufeinanderfolgende Kind-Elemente werden
also gegen ausgewählte Seiten des
übriggebliebenen Raums drangepackt, bis es
mehr den Platzt gibt.
Also ist es ganz relevant, zu
verstehen, dass der packer
Layout Manager zwischen packing
space und display space
unterscheidet. Der "Display-Raum" ist ein
Teil des Raums, das bestimmtes Kind-Element
für sich genommen hätte, um sich selbst
korrekt zu präsentieren. Zum Beispiel ein
"label" oder "button" Element hat ein
"display space", das ein wenig weiter als
das benötigte Raum für die Darstellung von
Text auf "label" oder "button" Widgets ist.
Das "packing space" ist dagegen das gesamte
Raum das verfügbar für die Positionierung
von Kind-Element im Hohlraum ist und das
kann größe oder kleiner als "display space"
für den Widget sein.
When "packing space" seine Maße
als dazu benötigte "display space"
überschreitet, tritt das "default"
Verhalten ein und zwar wird das
Kind-Element gerade ins Zentrum von "packin
space" gesetzt und das übrige Raum von
"packin space" wird anderen Seiten
verschoben. Wenn Sie stattdessen genau so
für das Kind-Element das ganze zur
Verfügung stehende Raum füllen möchten,
dann kann man den Parameter
fill setzen mit drei Werten
(x, y, both), die jeweilige Richtung
hindeuten, in welcher das Element gefült
wird. Wir werden das sehen später in
unserem Beispielprogramm
Ein noch damit verwandter
Parameter ist expand, welcher
macht nicht anderes als dem Kind-Element
hinweisen, wie es sich verhalten soll, wenn
das Hauptfenster sich in seiner Dimensionen
verändert. Default Einstellung beim
expand Parameter ist
false, gemeint ist damit
nämlich das, wenn Hauptfenster sich
vergrößert oder "schrumpft, wird der
Kind-Widget seine Größe und Position
beibehalten. Wenn man anstaat
expand als true
setzt, werden Kinder-Elemente auch ihre
Dimensionen entsprechend and die neue Größe
des Hauptfensters anpassen. Üblich ist es,
wenn man fill Parameter als
"both" festgesetzt hat für
einen bestimmten Kind-Widget, dann sollte
man expand als
"true" für diesen Kind-Widget
festlegen.
Als eine Vorführung, wie der
pack Layout Manager
funktioniert könnte es hilfreich sein an
einer konkreten Aufgabe hinzuschauen.
Erinnern Sie sich, dass wir uns mit einem
leeren rechtwinkligen "Hohlraum". Nun
lassen wir uns anfangen und fügen oben an
der Seite des Raums ein Kind-Element
hinzu(Widget 1 s.h.Abb.1):
Nach diesem Schritt hat sich
der erste Kind-Widget quasi die Seite oben
beanspucht. Ungeachtet, wie wir weitere
Kind-Widgets packen, das ist der einzige
Widget, welcher am oberen Rand des Raums
angegrenzt sein kann. Der unterste Rand des
ersten Elementes wird quasi "oberster" Rand
des übriggebliebenes Hohlraums. Als
Nächstes fügen wir am linken Rand des Raums
noch ein Kind-Widget 2(s.h.Abb.2)
Wieder einmal verringert sich
übriggebliebenes Raum, diesmal um die
Breite des zweiten Kind-Widgets. Der
unterste Rand des ersten Widgets immer noch
der oberste Rand des Raums, aber der
rechter Rand des zweiten Kind-Widgets wird
nun als neue linke Seite des verbleibenden
Hohlraum. Jetzt fügen wir den dritten
Kind-Widget 3(s.h.Abb.3) hinzu, diesmal am
untersten Rand des Raums.
Nachdem wir diesen dritten
Widget zugefügt haben, schrumpft das
übriggebliebenes Raum um die Höhe des
dritten Widgets und dessen oberste Rand
wird nun der neue unterste Rand des übrig.
Raums. Als Letztes fügen wir den letzten
Widget 4(s.h.Abb.4) hinzu, diesmal am
rechten Rand.
Ruby/Tk Beispielprogramm
Unten wird Quellecode gezeigt,
welchen Sie sich auch herunterladen können
tk-xmlviewer
#!/usr/bin/ruby
-w
# Sample
Application(Beispielprogramm)
tk-xmlviewer.rb
require "tk"
require "nqxml/treeparser"
class XMLViewer < TkRoot
def createMenubar
menubar =
TkFrame.new(self)
fileMenuButton =
TkMenubutton.new(menubar,
'text'
=> 'Datei',
'underline'
=> 0)
fileMenu =
TkMenu.new(fileMenuButton, 'tearoff' =>
false)
fileMenu.add('command',
'label'
=> 'Öffnen',
'command'
=> proc { openDocument },
'underline'
=> 0,
'accel'
=> 'Ctrl+O')
self.bind('Control-o',
proc { openDocument })
fileMenu.add('command',
'label'
=> 'Beenden',
'command'
=> proc { exit },
'underline'
=> 0,
'accel'
=> 'Ctrl+Q')
self.bind('Control-q',
proc { exit })
fileMenuButton.menu(fileMenu)
fileMenuButton.pack('side'
=> 'left')
helpMenuButton =
TkMenubutton.new(menubar,
'text'
=> 'Hilfe',
'underline'
=> 0)
helpMenu =
TkMenu.new(helpMenubutton, 'tearoff' =>
false)
helpMenu.add('command',
'label'
=> 'Info',
'command'
=> proc { showAboutBox })
helpMenuButton.menu(helpMenu)
helpMenuButton.pack('side'
=> 'right')
menubar.pack('side'
=> 'top', 'fill' => 'x')
end
def createContents
# Lists
listBox =
TkListbox.new(self) {
selectmode
'single'
background
'white'
font 'courier 10
normal'
}
scrollBar =
TkScrollbar.new(self) {
command proc
{ |*args|
listBox.yview(*args)
}
}
rightSide =
TkFrame.new(self)
attributesForm =
TkFrame.new(rightSide)
attributesForm.pack('side'
=> 'top', 'fill' => 'x')
TkFrame.new(rightSide).pack('side'
=> 'top', 'fill' => 'both',
'expand'
=> true)
listBox.yscrollcommand(proc
{ |first, last|
scrollBar.set(first,
last)
})
listBox.bind('ButtonRelease-1')
{
itemIndex =
listBox.curselection[0]
if
itemIndex
#
Remove currently displayed attributes
TkGrid.slaves(attributesForm,
nil).each { |slave|
TkGrid.forget(attributesForm,
slave)
}
#Add labels
und entry widgets for this entity's
attributes
entity =
@entities[itemIndex]
if
entity.kind_of?(NQXML::NamedAttributes)
keys
= entity.attrs.keys.sort
keys.each_index
{ |row|
TkLabel.new(attributesForm)
{
text
keys[row] + ":"
justify
'left'
}.grid('row'
=> row, 'column' =>0, 'sticky'=>
'nw')
entry
= TkEntry.new(attributesForm)
entry.grid('row'
=> row, 'column' => 0, 'sticky' =>
'e')
entry.value
= entity.attrs[keys[row]]
TkGrid.rowconfigure(attributesForm,
row, 'weight' => 1)
}
TkGrid.columnconfigure(attributesForm,
0, 'weight' => 1)
TkGrid.columnconfigure(attributesForm,
1, 'weight' => 1)
else
end
end
}
listBox.pack('side' =>
'left', 'fill' => 'both', 'expand' =>
true)
scrollBar.pack('side' =>
'left', 'fill' => 'y')
rightSide.pack('side' =>
'left', 'fill' => 'both', 'expand' =>
true)
@listBox = listBox
@attributesForm =
attributesForm
end
def initialize(root)
# Initialize base class
super
# Main Window Title
title 'TkXMLViewer'
geometry '600x400'
#Menu bar
createMenubar
createContents
end
def populateList(docRootNode,
indent)
entity =
docRootNode.entity
if
entity.instance_of?(NQXML::Tag)
@listBox.insert('end',
''*indent + entity.to_s)
@entities.push(entity)
docRootNode.children.each
do |node|
populateList(node,
indent + 2)
end
elsif
entity.instance_of?(NQXML::Text)
&&
entity.to_s.strip.length
!= 0
@listBox.insert('end',
''*indent + entity.to_s)
@entities.push(entity)
end
end
def loadDocument(filename)
@document = nil
begin
@document =
NQXML::TreeParser.new(File.new(filename)).document
rescue NQXML::ParserTree =>
ex
Tk.messageBox('icon'
=> 'error', 'type' => 'ok',
'title'
=> 'Fehler', 'parent' => self,
'message'
=> "Das XML Dokument konnte nicht
geparst werden")
end
if @document
@listBox.delete(0,
@listBox.size)
@entities =
[]
populateList(@document.rootNode,
0)
end
end
def openDocument
types = [["Alle Datei", "*"],
["XML Documents", "*.xml"]]
filename =
Tk.getOpenFile('filetypes' => types,
'parent' => self)
if filename != ""
loadDocument(filename)
end
end
def showAboutBox
Tk.messageBox('icon' =>
'info', 'type' => 'ok',
'title'
=> 'Über TkXMLViewer',
'parent'
=> self,
'message'
=> 'Ruby/Tk XML Viewer
Application')
end
end
# Run Application( Ausführen des
Programms)
root = XMLViewer.new
Tk.mainloop
Die ersten zwei Zeilen sind
einfach zum Importieren erforderlichen Tk
und NQXML Modulen. Danach wird
Hauptprogramm-XMLViewer Klasse
initialisiert, abgeleitet von
TkRoot. Die Methode dieser
Klasse initialize bekommt als
ein Argument root
def
initialize(root)
# Initialize base class
super
# Main Window Title
title
'TkXMLViewer'
geometry '600x400'
#Menu bar
createMenubar
createContents
end
Die erste Zeile dieser Methode
ruft super auf, um die
Base-Klasse zu initialisieren, das ist sehr
wichtig, ohhe das wird das Programm nicht
zum Vorscheinen kommen. Die nächste zwei
Zeilen rufen TkRoot's title
und geometry Methode auf,
beziehungsweise wird man der Titel des
Hauptfensters und dessen anfängliche Breite
und Höhe in pixel festgelegt. Diese zwei
Methoden sind übrigens von Ruby/Tk
Wm Module bereitgestellt,
welche die Anzahl der Funktionen fürs
Aufeinandererwirken mit "Window Manager"
definieren.
Die letzte zwei Zeilen der
initialize Methode rufen die
andere XMLViewer Methode
herbei. Damit werden Window's "Menüleiste"
und Contents Raum dargestellt.
Wir hätten wohl die Codes von diesen
Methoden direkt in die
initialize Methode
einschließen können, aber das würde andere
Teile der GUI Konstruktion sozusagen
zerbrechen, also das ist viel besser und
einfacher umfangreiche und schwierige
Programme aufzubauen, wenn Codes in
verschieden Methoden verteilt und so werden
wir weiter dieser Logik folgen. Im
Gegensatz zu einigen anderen Toolkits, wir
werden es sehen, hat Ruby/Tk keine
spezifische Klasse für die "Menüleiste";
stattdessen werden wir einfach einen
TkFrame-Widget Behälter
verwenden, ausgedehnt entlang der obersten
Rande des Hauptfensters.
Die "pulldown" Menü von Ruby/Tk
besteht aus dem TkMenubutton
Objekt verbunden mit dem
TkMenu Objekt. Der
TkMenubutton Widget ist es,
was man auf der Menü selbst sieht, der
beinhaltet den Text, was der Name der Menü
ist, wie z.B. Datei,
Bearbeiten oder
Hilfe. Wenn der Anwender aud
den Button klickt, der damit verbundenen
TkMenu Widget wird angezeigt.
Sie können ohne weiteres eine odere mehrere
Optionen zu TkMenu Widget
hinzufügen, wobei wird die add
Methode verwendet. Lassen wir uns an der
Organisation unseres Beispielprogrammes
ansehen, nälich an der "Datei" Menü:
fileMenuButton =
TkMenubutton.new(menubar,
'text'
=> 'Datei',
'underline'
=> 0)
Die Menü's Buttons werden als
Kind-Widget der Menüleiste selbst erzeugt.
Das underline Attribut von
TkMenubutton Widget ist eine
"integer"(ganze Zahl), welche auf eine
Buchstabe hinweist, die im Titel des
Menübuttons als unterstriechen bezeichnet
wird. Die Hervorhebung einer Buchstabe in
Menütitle ist ein allgemein-verwendeter
optischer Hinweis in GUI Programms, um
sozusagen eine "Beschleunigung-Lösung"
festzulegen, die uns erlaubt die Menü zu
aktivieren, z.Beispiel in meisten Windows
Applicationen würde die Tastekombination
Alt+D die "Datei" Menü
aktivieren.
Als Nächstes erzeugen wir
TkMenuWidget, verbunden mit
dem TkMenubutton und fügen für
diese Menü einen ersten Eintrag:
fileMenu =
TkMenu.new(fileMenuButton, 'tearoff' =>
false)
fileMenu.add('command',
'label'
=> 'Öffnen',
'command'
=> proc { openDocument },
'underline'
=> 0,
'accel'
=> 'Ctrl+O')
self.bind('Control-o',
proc { openDocument })
Beachten Sie dabei, dass
TkMenu als Kind-Widget von
Menübutton erzeugt wird. An dieser Stelle
legen wir fest, dass die Menüstil nicht
"abreißbar" ist. Der erste Eintrag, den wir
zu "Datei"-Menü hinzfügen ist
"command"-Entrag für Öffne
Befehl. Der erste Argument zu
add Methode ist ein "string",
der uns gerne verrät,um welche Type des
Menüeintrages sich handelt; zusätzlich zum
command Attribut gibt's noch
einige Type, wie z.Beispiel für den
"checkbutton" Eintrag ist das
check, "radiobutton" Eintrag
radio, "separator"
separator und für "cascading
sub-menu" cascade. Das
command Attribut für diesen
Menüentrag ist ein Ruby Proc
Objekt, welches eine andere XMLViewer
"Instanz"-Methode aufruft, in diesem Fall
openDocument, welche wir uns
noch ansehen werden. Das accel
Attribut legt die Tastaturkombination, die
uns eine Möglichkeit gibt, auf
schnelleweise eine Datei auszuwählen und
sie zu öffnen. Der String dafür steht
hinter dem "Offnen" Menüeintrag, was aber
nicht automatisch für diese
"Beschleunigungskombination" eingebunden
wird, wir müssen dafür die
bind Methode verwenden Damit
wird diese Tastaturkombination in unserem
Hauptfenster bzw. Programm eingebunden.
Nun lassen wir uns, auf von uns
definierte createContents
Methode einen Blick werfen. Diese Methode
baut einen Raum für einen Inhalt(Content)
auf dem Hauptprogrammfenster auf, wobei er
in zwei Teilen getrennt wird. Ein Teil auf
der linken Seiteist für eine Aufstellung
von XML Dokumentenknoten und auf der
rechten Seite eine Aufstellung des
Knotenattributes.
listBox =
TkListbox.new(self) {
selectmode
'single'
background
'white'
font 'courier 10
normal'
}
scrollBar =
TkScrollbar.new(self) {
command proc
{ |*args|
listBox.yview(*args)
}
}
listBox.yscrollcommand(proc
{ |first, last|
scrollBar.set(first,
last)
})
Tk's "list-box" Widget zeigt
eine Liste von "strings", von denen wir uns
etwas aussuchen können. Unsere
TkListox Instanz bestimmt das
selectmode Attribut zu
single, was darauf hinweist,
dass nur einen Posten zur gleichen Zeit
ausgewählt werden kann. Es gibt auch andere
"selectionmode", die es mehrfach erlauben
,zur gleichen Zeit Posten auszuwählen(unter
Posten versteht man z.Beispiel einen
Eintrag in XML-Datei). Wit setzen auch das
Attriubut font als "Courier
mit der Größe 10" fest, anstatt eine
Default Schriftart zu verwenden, welche
gewöhnlich die systemabhängige und
proportional-raumige Schriftart ist. Da die
Liste von Posten(Einträgen) sehr lang sein
könnte(zu viel um auf einer Seite des
Bildschirmes zu passen), werden wir zu
diesem "Listbox" einen
TkScrollbar Widget hinzufügen.
Der Codeblock oder eine Prozedure, woraus
die "scrollbar's command
Methode besteht, modifiziert die Ansicht
des "listboxes", bzw. das Aufstellen von
Posten, die in diesem Widget gezeigt
werden. Die Anzahl von Parametern, die an
"scrollbar" command Methode
übergeben werden, variiert abhängig davon,
wie oft der Anwender den Scrollbar betätigt
hat. Zum Beispiel, wenn der Anwender die
Position von "scrollbar" durch das
Betätigen einer von zwei "Pfeiltasten"
bestimmt, empfängt die command
Methode drei
Argumente(scroll,1,units). Um
mehr Information über verschiedene
Argumente für den TkScrollbar
Widget zu bekommen, sollten Sie die Tk
Reference Dokumentation zur Rate ziehen. Im
Allgemeinen braucht man überhaupt nicht,
sich damit zu beschäftigen, wie man für die
"command" Methode passende Argumente
besorgt, man gibt sie einfach der
xview oder yview
Methode über. So ähnlich wird ein Codeblock
oder eine Prozedur an die
yscrollcommmand Methode
weitergeleitet, damit wird die Position und
das Aufstellen von "scrollbar" , when sich
"list contents" verändert, angepasst. Der
nächste Abschnitt unseres Programmcodes
legt die rechte Seite des Hauptfensters
fest.
rightSide =
TkFrame.new(self)
attributesForm =
TkFrame.new(rightSide)
attributesForm.pack('side' =>
'top', 'fill' => 'x')
TkFrame.new(rightSide).pack('side'
=> 'top', 'fill' => 'both',
'expand'
=> true)
Im Moment ist es nicht
besonders interresant, weil die
"attributes" Formular noch leer ist. Aber
der nächste Teil von Programmcode, welcher
mit der Auswahl von Einträgen in XML-Datei
handelt, macht diese Absicht ein wenig
deutlicher:
listBox.bind('ButtonRelease-1')
{
itemIndex =
listBox.curselection[0]
if
itemIndex
#
Remove currently displayed attributes
TkGrid.slaves(attributesForm,
nil).each { |slave|
TkGrid.forget(attributesForm,
slave)
}
#Add labels
und entry widgets for this entity's
attributes
entity =
@entities[itemIndex]
if
entity.kind_of?(NQXML::NamedAttributes)
keys
= entity.attrs.keys.sort
keys.each_index
{ |row|
TkLabel.new(attributesForm)
{
text
keys[row] + ":"
justify
'left'
}.grid('row'
=> row, 'column' =>0, 'sticky'=>
'nw')
entry
= TkEntry.new(attributesForm)
entry.grid('row'
=> row, 'column' => 0, 'sticky' =>
'e')
entry.value
= entity.attrs[keys[row]]
TkGrid.rowconfigure(attributesForm,
row, 'weight' => 1)
}
TkGrid.columnconfigure(attributesForm,
0, 'weight' => 1)
TkGrid.columnconfigure(attributesForm,
1, 'weight' => 1)
else
end
end
}
Dieser gesamte Codeblock ist
mit ButtonRelease Ereignis
festgebunden, wenn man mit linken Maustaste
in Listbox auf eine von Einträgen klickt.
Das ist das Ereignis, das erzeugt wird,
wenn der Anwender einen von Listeinträgen
auswählt und darauf klickt und dann einfach
den linken Mausbutton auf den ausgewählten
Eintrag loslässt. Wir beginnen mit dem
Aufruf der TkListbox's
curselection Methode um in
einem Array Indexes von ausgewählten
Einträgen zu speichern. Da es eine
"singl-selection" Liste ist, erwarten wir
nur einen ausgesuchten Eintrag und zwar als
Element, das den Index "0" im Array hat.
Als Nächstes löchen wir aktuellen
attributes Formularsinhalt,
erreichen wir es, wenn man
TkGrid.slaves aufruft, die in
der Form eines Arrays die Kind-Widgets von
Formularen erhält, und dann wenn man
TkGrid.forget aufruft, wird
man mit der Hilfe each Methode
durchgeforstet und gelöscht. Während die
meisten GUI Toolkits die "parent-child"
Terminologie verwendet, um auf den
hierarchischen Aufbau von GUI' Containers
zu verweisen, verwendet Tk Häufig Termine
"master" und "slave", vor allem when man so
einen Layout Manager als
TkGrid einsetzt.
Der nächste Abschnitt dieses
"Event-Handler" "schleift" über alle
Attributes des aktuell ausgewählten
Dokumentenknotens und fügt für jeden Knoten
einen TkLabel und
TkEntry Widgets hinzu. Merken
Sie es, dass wir die Default-Einstellung
für Ausrichtung von TkLabel
und TkEntry Widgets nicht
berüksichtigen. Gültige Werte für das
justify Attribut sind
left, center und
right, aber die
Grundeinstellung der "Label's" Ausrichtung
ist center. Also weil wir gern
sehen würden, dass "grid" Manager Spalten
und Zeilen gleich behandeln wird, rufen wir
TkGrid.rowconfigure und
TkGrid.columnconfigure
Module-Methode auf, um das
weight Attribut für jede
Spalte und Zeile auf "1" zu setzen.
Nun sehen wir uns noch weitere
sogennante "lower-level" Methoden für
unsere XMLViewer Klasse. Für
den Anfang haben wir
openDocument Methode, welche
ausgeführt wird, sobald der Anwender den
Offnen Eintrag in der
Datei Menü auswählt oder
"Shortkey" Ctrl+O
verwendet:
def
openDocument
types = [["Alle Datei", "*"],
["XML Documents", "*.xml"]]
filename =
Tk.getOpenFile('filetypes' => types,
'parent' => self)
if filename != ""
loadDocument(filename)
end
end
Der wichtigste Teil dieser
Funktion ist das Aufrufen von
Tk.getOpenFile Widget und
selbstdefinierter loadDocument
Methode. Tk.getOpenFile ist
eine Tk Module-Methode, die
man ermöglicht, das systemspezifisches
Datei-Dialogbox anzuzeigen, wo man sich
existierende Dateinamen aussuchen kann.
Ähnliche Funktion hat
Tk.getSaveFile Modul-Methode,
die man verwenden kann, wenn man eine von
existierenden oder neuen Datei speichern
will. Das filetypes Attribut
setzt eine Liste von Dateitypen als eine
Schilderung und ein Muster dafür fest. Das
nächste Attribut parent legt
den Besitzer owner des
Fensters für den Datei Dialog
fest. Angenommen, dass der Anwender nicht
disen Dialog beendet und versorgt sich mit
einem legitimen Dateinamen, dann wird
unsere selbstdefinierte
loadDocument Methode
aufgerufen um die aktuelle, ausgesuchte
XML-Datei auszulesen:
def
loadDocument(filename)
@document = nil
begin
@document =
NQXML::TreeParser.new(File.new(filename)).document
rescue NQXML::ParserTree =>
ex
Tk.messageBox('icon'
=> 'error', 'type' => 'ok',
'title'
=> 'Fehler', 'parent' => self,
'message'
=> "Das XML Dokument konnte nicht
geparst werden")
end
if @document
@listBox.delete(0,
@listBox.size)
@entities =
[]
populateList(@document.rootNode,
0)
end
end<
Wenn der XML Parser eine
Ausnahmesituation meldet während des
Auslesens von XML-Datei, kann man sich ein
einfaches Dialogbox anzeigen lassen,
dargestellt mit der Hilfe von der
Tk.messageBox Module-Methode.
Das icon Attribut kann ein von
vier "strings" aufnehmen und zwar
error, info,
question,
warning, um einen optischen
Effekt zu liefern. Das type
Attribut weist darauf hin, welcher der
"Ende-Button" verwendet wird. Für unseren
einfachen Fall werden wir einfach den
Button als "OK" anzeigen lassen und beim
Drüclken auf den Button wird dieser kleine
Dialog beendet. Es gibt natürlich auch
andere Optionen fürs type
Attribut. Das sind
abortretrycancel,
okcancel,
retrycancel,
yesno und
yesnocancel.
Angenommen, es gab keine Fehler
beim Auslesen von XML-Document, die
Instanz-Varible @document eine
Referenz(einen Verweis) auf ein
NQXML::Document Objekt
besitzen. Wir rufen die delete
Methode auf, um die aktuelle Liste zu
löschen und dann wird die Methode
populateList gestartet, welche
die Liste mit dem neuen Documentinhalt
ausfüllt:
def
populateList(docRootNode, indent)
entity =
docRootNode.entity
if
entity.instance_of?(NQXML::Tag)
@listBox.insert('end',
''*indent + entity.to_s)
@entities.push(entity)
docRootNode.children.each
do |node|
populateList(node,
indent + 2)
end
elsif
entity.instance_of?(NQXML::Text)
&&
entity.to_s.strip.length
!= 0
@listBox.insert('end',
''*indent + entity.to_s)
@entities.push(entity)
end
end
Andere GUI Toolkits, wir werden
später es sehen, haben eine baumähnliche
Struktur für die Dastellung von XML-Datein.
Tk hat aber solche Möglichket nicht, es
gibt allerdings eine TK's Erweiterung -
"Tix". Mehr Information über Tix erhalten
Sie im Abschnitt "Tk's Erweiterung: Tix und
BLT". Da Tix nicht immer verfügbar ist,
werden wir die nur ungefähr behandeln, aber
wenn Sie sich keine Gelegenheit "vermissen"
möchten, wie man den baumstrukturigen
Widget in einer regulären
TkListbox mit der Einrücken
von Listeinträgen verwendet, sollten Sie
sich diese Erweiterung näher ansehen. Die
populateList Methode wird
solange aufgerufen, bis das ganze Document
dargestellt wird und anschließend sehen
wir, wie die Applikation die Auswahl der
Liste von Einträgen behandelt.
Und so sieht unsere fertige
Ruby/Tk Applikation aus(das Programm wurde
unter Windows XP getestet):
Für Linuxbegeisterten, zu denen
ich mich auch zähle, würde es so
aushehen:
Anwenden SpecTcl GUI Builder
GUI-Building Tool für Tk SpecTcl
war ursprünglich von "Sun Microsystems"
entwickelt worden. Seit schon geraumiger
Zeit wird dieses Projekt von einer Gruppe
Freiwilligen unter Leitung von Morten
Jensen weiterentwicklet und gewartet.
Obwohl die ursprüngliche Absicht von
"SpecTcl" war, die auf "user interface"
basierende Tcl Sourcecodes zu erzeugen,
haben Entwickler seitdem eine Generation
kompatiblen Codes für andere Skriptsprachen
enwickelt(Perl, Python, und Ruby), welche
Tk als GUI verwenden. Eine experimentale
Version von Ruby "backend" für "SpecTcl",
genannt als specRuby wurde von
Conrad Schneiker entwickelt und gewartet
von Jonathan Conway. Eine aktuelle Version
kann man von RAA-specRuby
herunterladen. Folgende Abbildungen zeigen
das Programm selbst mit einem einfachen Tk
"user interface":
und als ein Ergebnis, wenn man
das testet:
Zur Zeit gibt's keine Homepage
von "specRuby", aber wie gesagt das Projekt
kann man in "RAA" finden. Um Ruby/Tk zu
verwenden, muss selbstverständlich eine
funktionierende Tcl/Tk Installation auf dem
System vorhanden sein.
Verwendung der Tk Erweiterung: Tix und
BLT
Zusätzlich zu Tk's sozusagen
"Kern-Widgets", gibt's aus viele Widgets
von "dritter Hand", die mit Tk compatibel
sind. Während die Beschaffung von dieser Tk
Erweiturung jemandem zu schaffen macht, ist
aber das Resultat dieser Anstrengung eher
verblüffend, als man nur Tk allein
einsetzt. Einer villeicht der bekannsten
Tk-compatiblen Erweiterungen ist
Tix- "Tk Interface Extension"
genannt (http://tix.sourceforge.net)
Tix bietet einen hierarchischen List-Widget
(baumstrukturige Liste),einen
"notebook-widget" und auch
"combo-box-widget" sowie andere widgets an.
Sie können immer den Source-Code von Tix
Homeseite herunterladen, aber um diese
Erweiterung zu verwenden, müssen Sie Tcl/Tk
Source-Code herunterladen und selbst
kompilieren.
Eine andere bekannte
Erweiterung für Tk ist BLT(www.tcltk.com/blt) Als
Tix bringt BLT eine Vielfalt von neuen
Möglichkeiten, meistens für die Erstellung
von Diagrammen und Grafiken. Sie können den
Source-Code oder schon vorkompilierte
Binäries für Windows von der BLT Homeseite
herunterladen, aber im Gegensatz zu Tix ist
die Erstellungsprozedure ganz normal, wie
man si aus der Unix-Welt sicher kennt.:
configure,
make und make
install. Um Tix und BLT mit
Ruby/Tk zu verwenden müssen Sie ein
TclTk-ext Package von Hidetoshi Nagai. Das
können Sie von RAA downloaden, aber alle
Dokumentation dazu wird in Japanisch
geschrieben.