Verwendung GTK+ Toolkit
GTK+ ist ein cross-platform GUI
Toolkit, der ursprünglich entwickelt worden
war, um "GNU Image Manipulation
Programm(GIMP) zu unterstützen. Wie schon
der Name sagt, ist GIMP ein
Bildbearbeitungsprogramm(www.gimp.org), das sich
mit vielen kommerzielen Programmen messen
lässt. Obwohl GTK+ hauptsächlich für X
Window System entworfen wurde, ist es auch
auf Microsoft Windows portiert worden. Als
ein Teil des GNU Projektes wird GTK+ für
viele populäre freie Software verwendet und
er ist sozusagen ein Kernkomponent des GNU
Projektes "GNU Network Object Model
Environment(GNOME)-Desktop Umgebung"
Ruby/GTK ist eine Ruby's
Erweiterung-Module, die in "C" geschrieben
wurde. Sie bietet eine Schnittstelle von
Ruby zu GTK+ an. Diese Erweiterung war
ursprüglich von Yukihiro Matsumoto(der
Author Ruby) und zur Zeit wird das Projekt
von Hiroshi Igarashi gewartet.
Installieren Ruby/GTK
Die Homeseite von Ruby/GTK ist
www.ruby-lang.org/gtk.
Wenn Sie schon ein Standard Ruby
Istallation für Windows von der "Pragmatic
Programmers' site" haben, ist es
wahrscheinlich, dass man auch
vorkompilierte Binäres für Ruby/GTK hat.
Für andere Platformen(Unix und Linux) muss
man Ruby/GTK von Sourcecoden selbst
kompilieren
Um Ruby/GTK zu kompilieren muss
man unbedingt schon eine GTK+ Installation
haben. Viele Linux- Distributions
beinhalten schon eine GTK+ Development
Package als eine Installationsoption; Red
Hat z.B. hat eine gtk+-devel
Package. Wenn ihre Distrubition schon keine
GTK+ Installation hat, haben Sie eine
Möglichkeit, sich auf GTK+
Homeseite(www.gkt.org) reichlich mit der
Information, was und wie heruntergeladen
und installiet wird, zu bedienen. Dann
müssen Sie sich auch Ruby/GTK Sourcecode
besorgen und wenn man schon alles hat, kann
man mit der Installation anfangen.
Erst wenn man funktonfähige
GTK+ Installation hat, muss man mit
Ruby/GTK Package beginnen. Zur Zeit ist die
aktuelle Version von "ruby-gtk" 0.34. Auf
der entsprechenden Seite gibt's auch die
komplete "ruby-gnome" Source-Code. Ich rate
Ihnen, die komplete Package herunterzuladen
und zu installieren. Nachdem Sie alle
Vorbereitungen getroffen haben, entpacken
Sie Package, die als tar.gz gepackt ist.
Mit dem schon bekanntem Befehl tar
xzfv ruby-gtk-0.34.tar.gz entpacken
Sie die Package und danach wechelsn Sie in
das enpackte Verzeichnis und führen Sie den
folgenden Befehle ruby
extconf.rb aus. Damit wird
automatisch Makefile erzeugt. Daraufhin
geben Sie an der Konsole make
ein, um der Code zu kompilieren.
Anschließend führen Sie als root make
install aus.
Ruby/GTK Basis
Obschon die GTK+ in "C"
geschrieben wurde, ist der Aufbau der
Bibliothek object-orientierend und deswegen
spiegelt Ruby/GTK derer "Klass-Hierarchie"
wieder. When Sie bereits mit "GTK+ C"
API(Application Programm Interface)
vertraut sind und auch schon solchen
Widgets Namen wie GtkLabel, GtkButton,
etc., begegnet sind, dann ein Übergang zur
Programmierung mit Ruby/GTK fällt Ihnen
bestimmt leicht. GTK+ Widgets werden in der
Form GtkWidgetName benannt und
sind in Ruby/GTK als
Gtk::WidgetName zu definieren.
Das heisst, dass der Ruby Modulename
Gtk ist, und der Name der
Widget Klasse ist WidgetName.
Und so ähnlicherweise sind Ruby/GTK
Instanzmethoden den entsprechenden "C"
Funktionnamen ähnlich, z.B. "C" Funktion
gtk_label_set_text() wird in
Ruby/GTK Gtk::Label mit der
Instanzmethode set_text
Wir werden nun ein kleines
Ruby/GTK Programm erstellen, das aus dem
Hauptfenster und einem oder mehreren Kind
Widgets besteht, dann setzen wir sogennante
"signal handler" ein, und wenn das Programm
starten, wird die Steuerung dem GTK+ "main
event loop" übergeben. Ohne weitere
Umstände presentieren wir eine Ruby/GTK
Version von "Hello World":
require 'gtk'
window = Gtk::Window::new
button = Gtk::Button::new("Hello
World!")
button.signal_connect(Gtk::Button::SIGNAL_CLICKED)
{
puts "Goodbye,
World!"
exit
}
window.add(button)
button.show
window.show
Gtk::main
Nun kann man unseres Programm
unter einem Namen speichern z.B.
gtk_hallo.rb und danach laufen
lassen, tippen Sie auf der Konsole:
ruby gtk_hallo.rb ein:
Als Ergebnis bekommen wir
unseres kleines Ruby/GTK Programm:
Und wenn wir anschließend den
Button betätigen, würd es so aussehen:
Das Programm beginnt mit der
Einbindung der erforderlichen Ruby/GTK
Bibliothek: gtk. Als Nächstes
erstellen wir zwei neue Widgets: GtkWindow
Widget, welcher standardmäßig als
"top-level" Hauptfenster gestartet wird und
zweiter Widget ist GtkButton Widget mit der
Beschriftung "Goodbye World!". Wie Sie
villeicht schon gemerkt haben, bis dahin
gab es keine Verbindung zwischen diesen
beiden Widgets. Mit Ruby/Gtk bilden Sie
ihre ganze "User Inteface" mit wenigen
Schritten am Ende, wenn man Kinder's
Widgets zu Vater's Widget hinzufügt.
Die nächste Zeile zeigt uns
eine Art von Ruby/GTK "event handler". Der
Code innerhalb des Codeblockes wird nicht
sofort ausgeführt, stattdessen verbindet
Ruby/GTK dieser Codeblock mit dem Signal
vom Button
Gtk::Button::SIGNAL_CLICKED,
welches später generiert wird, sobald das
Programm läft. Im nächsten Abschnitt werden
wir ausführlicher darauf eingehen, aber
jetzt lassen wir uns einfach auf den Button
klicken, das Programm antwortet uns damit,
dass auf der Konsole "Goodbye World!"
erscheint, und gleichdanach beendet sich
Programm selbst.
Die nächste zwei
Zeilen(button.show und window.show) sind
entscheidend, die sind etwas Einzigartgiges
in Ruby/GTK, was man in andreen Toolkit's
nicht trifft. Standardmäßig sind neue
erstellte Widgets nicht zu sehen, man muss
danach explizit mit der Methode
show darauf hinweisen, dass
die Widgets sichtbar werden. Dieser Schritt
wird häufig bei neuen Ruby/GTK
Programmieren vergessen. Und die letzte
Zeile des Programmes ruft GTK+ "main event
loop"(Programmschleife). Ab diesem
Zeitpunkt wartet das GTK+ Programm auf
ihren Input, passt sozusagen besonderes auf
Signale(wie "Buttonclick") auf, für welche
man schon "signal handler" Methode
definiert wurde. Nun aber lassen wir uns
mehr detalliert darauf einen Blick werfen
und sehen wir uns, wir das in "wirklichen"
Programm funktioniert.
Programmsignale und Signal
Handlers
Ruby/GTK Ereignis Model basiert
auf die Idee, dass man etwas
geschieht(gemeint wird damit z.B. User's
Handlungen), z.B. geben "user interface
objects(Widgets) signale ab.
Viele von diesen Signale sind "low-level"
Ereignisse und werden von Betriebsystem
generiert und zeigen generelle Information
solche wie "die Maus wird bewegt" oder "die
linke Maustaste wird geklickt". Andere
Signale werden von GTK+ selbst erzeugt und
bieten eher bestimmte Information an, so
wie z.B. "ein Listeneintrag wird gewählt"
Ein Widget kann eine beliebige Anzahl von
Signalen aussenden und jedes Signal hat in
Ruby/GTK einen Namen, welcher auf seine
Bedeutung hinweist. Zum Beispiel
GtkButton Widget sendet ein
"clicked" Signal aus, when der Button
geklickt wird. Da GTK+ ein
objekt-orientierender Toolkit ist, kann ein
bestimmter Wigdet nicht nur seine
widget-spezifische Signale abgeben, sondern
auch diejenige, die seinem "Vorfahr-Klasse"
gehöhren.
Um ein Signal von einem Widget
mit irgendeiner Aktion zu verbinden, kann
man eine Widget's
signal_connect Methode
aufrufen. Diese Methode nimmt ein
String-Argument ein, der auf den Signalname
hinweist(wie in unserem Beispiel "clicked")
und der Coderblock wird in Zusammenhang des
Aufrufers ausgewertet. Zum Beispiel, wenn
ihre Ruby/GTK-basierendes
Tabellenkalkulationsprogramm ein Inhalt in
der schon vorhandenen Tabellendatei
speichern möchte, jedesmal wird der
Speichern Button
geklickt,dann kann man solche Zeilen im
Programm einschließen:
saveButton.signal_connect('clicked')
{
saveSpreadsheetContents
if contentsModified?
}
Jede Klasse definiert eine
symbolische Konstante für den Namen des
Signales, das die abgeben kann und diese
Konstante kann anstatt von "literal
string"(in unserem Fall "clicked")
verwendet werden. Zum Beispiel im Falle des
Codes, den wir oben geschrieben haben,
würde es so aussehen:
saveButton.signal_connect(Gtk::Button::SIGNAL_CLICKED)
{
saveSpreadsheetContents
if contentsModified?
}
Der Vorteil der Verwendung der
symbolichen Konstante ist es, wenn Sie
einen Screibfehler machen, werden Sie
wahrscheinlich viel schneller den Fehler
entdecken, sobald Sie das Programm laufen
lassen. Wenn Sie versuchen eine Konstante
zu referenzieren und haben Sie dabei nicht
richtig den Namen der Konstante
geschrieben, beschwert sich Ruby und das
Programm wird mit der Ausnahmefehler
NameError beendet. Wenn Sie
den literalen String verwenden, hat Ruby
keine Möglichkeit zu überprüfen, ob das
gültige Signalname war, bevor man den die
Methode signal_connect
weitergegeben wird.
Als Programmier ist es
entscheidend, welche Widget's Signal vom
besonderen Interesse sind und wie ihres
Programm würde reagieren, when solche
Signale ausgesendet werden. Ebenfalls
achten Sie darauf, wenn es einen Sinn für
ihres Programm macht, können Sie das
gleiche Signal(vom gleichen Widget) mit
mehreren Codeblocken. In diesem Fall die
Signal Handler werden ausgeführt und zwar
in der Reihenfolge, in der sie anfänglich
verbunden waren. Wenn wir mit unserem
Beispielprogramm anfangen, sehen wir ein
paar Beispiele, wie die Signale mit
Codeblocken verbunden werden können. Für
die vollständige Auflistung von Signalen,
die von verschiedenen Ruby/GTK Widgets
ausgesendent werden, schlagen Sie bitte die
Online-Dokumentation auf der Ruby/GTK
Homeseite(www.ruby-lang.org/gtk) nach.
Arbeitsweise mit Ruby/GTK Layout
Manager
Genauso wie Ruby/Tk bietet
Ruby/GTK eine Auswahl an flexiblen Layout
Managern an. Jeder von drei Layout
Managern, wir werden das betrachten, ist
ein Behälter(Container)-Widget, zu dem Sie
ein oder mehrere Kinder-Widgets hinzufügen
können. Der Container selbst ist für alle
praktische Zwecke unsichtbar. Die erste
zwei Layout Manager horisontal
packing box(Gtk::HBox) und
vertical packing box
(Gtk::VBox) ordnen iher Kinder-Windgets in
Zeilen bzw. Spalten an. Der dritte Layout
Manager "Gtk::Table" ordnet seine
Kinder-Widgets in der tabellarischen Format
an, genauso wie Ruby/Tk's "grid layout.
Der "horisontal packing box"
Layout Manager(Gtk::HBox) ordnet seine
Kinder-Widgets, wie der Name schon sagt,
horisontal. Alle Kinder haben die gleiche
Höhe, aber ihre Breite können sich
entsprechend Parametren von "packing box"
anpassen. Im Gegensatz zu Gtk::HBox ordnet
Gtk::VBox seine Kinder-Widgets vertical an,
und sie alle haben die gleiche Breite. Da
diese zwei Layout Manager gleichartig sind,
konzentrieren wir uns auf Gtk::HBox. Die
new Methode für den Gtk::HBox
nimmt zwei Argumenten ein:
hbox =
Gtk::HBox.new(homogeneous=false,
spacing=0)
Der erste Argument ist ein
Booleanischer Wert, der darauf hinweist, ob
die Kinder Widget's Größen
homogeneous(gleichartig) oder
nicht sind. When dieser Argument "true"
ist, heisst es einfach, dass die " packing"
Box ihre Breite zwischen ihrer
Kinder-Widgets teilen wird; when der Wert
"false"(nicht gleichartig) ist, heisst es,
dass jeder Kind-Widget so viel Platzt
beansprucht, so viel er braucht, nicht
mehr. Und der zweiter Argument ist ein in
pixel gemessener Raum, der zwischen
einzelnen Kinder-Widgets ist.
Man kann ein Kind-Widget zu der
"packing" Box hinzufügen, wenn man ein von
beiden pack_start oder
pack_end Instanz-Methoden
verwendet. Sie können sich bestimmt
erinnern, dass wir in Ruby/Tk den
Eltern-Widget als erster Argument zu der
Methode new übergeben haben,
um einen neuen Kind-Widget zu erstellen.
Ruby/Gtk ergreift dabei eine andere
Methode: Kinder-Widget werden zuerst
erzeugt und dann zum Behälter-Widget
hinzugefügt oder gepackt. Um einen
Kind-Widget zu Gtk::HBox oder Gtk::VBox zu
packen, können pack_start
Methode aufrufen.
hBox.pack_start(kind,
expand=true, fill=true,
padding=0)
Der erste Argument von der
pack_start Methode ist einfach
ein Verweis auf den Kind-Widget, den zur
"packing" Box hinzufügen möchten, weitere
drei Argumente efordern aber eine
ausführliche Erklärung. Der zweite Argument
expand ist quasi eine
Anweisung zur "packing" Box, dass der
Kind-Widget auszudehnen kann, wenn das
möglich wäre(z.B. User ändert das Fenster,
um das zu größer zu machen). Wir haben
schon erläutert, worin sich
"homogeneous"(gleichartige) und
"non-homogeneous"(nicht-gleichartige)
"packing" Boxes unterscheiden;
"homogeneous" "packing" Box teilt sein Raum
gleichmäig zwischen den Kinder-Widgets.
Eine andere Weise, das sich zu
vorzustellen, ist es, dass jeder
Kind-Widget sich so viel Platz nimmt, so
viel sich der breiteste Widget genommen
hat. Die Nebenwirkung dieser Einstellung
ist es, dass die Kinder-Widgets sich genau
in der Mitte des für sie festgelegten
Raumes plazieren würden. Wenn der Argument
fill den Wert "true" bekommt,
heisst es, dass der Kind-Widget sich in
seinem festgelegten Raum wachsen kann. Der
letzter Argument der Methode
pack_start weist einfach auf
die "Füllung"(in pixel), sozusagen den
Abstand rund um den Kind-Widget bis zu dem
Rand des f&uuur ihn festgelegten Raum,
der Argument ist ein Zusatz zu dem schon
erwähnten Argument spacing von
der Methode "new". Wennn der Kind-Widget
entweder erster oder letzter Widget in der
"packing" Box ist, ist der Wert von diesem
Argument ein Abstand vom Rand bis zu diesem
Kind-Widget.
Ruby/GTK bietet auch Gtk::Table
Layout Manager an. Dieser Manager ordnet
die Kinder-Widgets in Zeilen und Spalten
an. Die new Methode von diesem
Layout Manager bekommt drei Argumente:
table =
Gtk::Table.new(numRows, numColumns,
homogeneous=false)
Die erste zwei Argumente sind
die Anzahl von Zeilen und Spalten in der
Tabelle und der "homogeneous" haben wir
schon vorhin erklärt. Um den Abstand
zwischen Zeilen und Spalten festzulegen,
verwendet man eine von der
Instanz-Methoden:
table.set_row_spacing(row,
spacing)
table.set_row_spacings(spacing)
table.set_column_spacing(column,
spacing)
table.set_column_spacings(spacing)
Die
set_row_spacings und
set_column_spacings Methoden
setzen den globalen Wert für den Abstand(in
pixel) fest, welcher für alle Spalten und
Zeilen in der Tabelle gilt. Aber wenn Sie
doch mehr expermentieren und selbst die
Anordnung definieren möchten, können Sie
set_row_spacing oder
set_column_spacing verwenden.
Damit kann man den Raum, der sich unter
einer bestimmten Zeile oder rechts von
einer bestimmten Spalte befindet. Um diesen
Wert festzusetzen, ruft man
set_row_spacing und
set_column_spacing Methoden
auf, die den globalen Wert überschreiben.
Also wenn man ein Kind-Widget zu Gtk::Table
hinzufügen möchte, verwendet man eine
attach Methode:
table.attach(kind, left, right,
top, bottom,
xopt=GTK_EXPAND|GTK_FILL,
yopts=GTK_EXPAND|GTK_FILL,
xpad=0,
ypad=0)
Das erscheint viel komplexer,
als die Argumentenliste von
pack_start oder
pack_end Methoden, man sollte
sich merken, dass die letzte vier
Argumenten Default-Werten haben. Der erste
Argument refenziert den Kind-Widget, der
hinzugefügt wird und die nächste vier
Argumente sind "integer"(ganze Zahlen), die
darauf hinweisen, wo der Kind-Widget in der
Tabelle plaziert wird und wieviel Zeilen
und Spalten der umfasst. Um besser zu
verstehen wie diese Argumente verwendet
werden, stellt sich ein Bündel von Linien
vor, die eine Tabelle mit ihrer Zellen
bilden, anstatt die Tabellenzellen selbst.
Zum Beispiel betrachten wir eine Tabelle
mit 5 Spaten und 3 Zeilen(s.h Abbildung
unten). Um das zu zeichnen müssen Sie 6
vertikale Linien(eine links und jede rechte
Seite von 5 Spalten) und auch 4 gorisontale
Linien(eine Linie oben und jede weitere von
3 Zeilen).
Mit dieser Abbildung wird
gemeint, dass die Bedeutung von
left, right,
top und bottom
Argumenten für Gtk::Table.new Folgendes
sind:
- Left zeigt,
welche vertikale Linie der Tabelle der
linke Rand vom Kind-Widget ist
- Right
zeigt, welche vertikale Linie der Tabelle
der rechte Rand vom Kind-Widget ist
- Top zeigt,
welche horizontale Linie der Tabell der
oberste Rand vom Kind-Widget ist
- Bottom
zeigt, welche horizontale Linie der
Tabell der unterste Rand vom Kind-Widget
ist
Für die Widgets, die lediglich
eine Tabellenzelle belegen, ist der Wert
für right immer um eins mehr
als der Wert für left und der
Wert für bottom um eins mehr
als für top. Aber für Widgets,
die vier oder fünf Spalten und drei oder
vier Zeilen der Tabelle, dann könnten Sie
wie jetzt vorgehen:
table.attach(kind, 3, 5,
1, 3)
Wenn die grafische Darstellung
nicht korrekt aussieht, überprüfen Sie die
Werte, die Sie an die Methode
attach übergeben haben.
Ruby/GTK wird sich beschweren, wenn die
Argumente,die man beifügt um eine Zelle zu
erzeugen, die Null Breite oder Höhe
hat(wenn left ist kleiner oder
gleich als right oder
top ist kleiner oder gleich
als bottom). Aber meistens
wird Ruby/GTK die Tatsache akzeptieren,
dass die Werte von left,
right, top oder
bottom falsch sind, selbst
wenn die Tabellenzellen sich überscheiden
werden
Die xopts und
yopts Argumente legen es fest,
wie die Tabelle sich einen zusätzlichen
Plazt für ihre Kinder-Widgets schaffen
würde. Gültige Werte für die beigen
Argumente wären z.Beispiel, GTK_EXPAND,
GTK_FILL oder GTK_EXPAND|GTK_FILL(beide
expand und fill). Die Bedeutung von diesen
zwei "flags" ist genau gleich, wie
entsprechende Parameter für die pach_start
und pach_end Methoden von der "packing"
Box. Und als Letztes legen zwei Argumente
xpad und ypad
horizontalen und vertikalen Abstand(in
pixel) fest, und das gilt als ein Füller,
der um einen Widget herum ensteht. Diese
Argumente sind als ein Zusatz zu schon
früher bekannten Einstellungen, die wir für
die Tabelle in Gtk::Table.new angewendet
haben.
Ruby/GTK Beispielprogramm
Unten wird der Quellecode
vorgeführt, welchen Sie sich auch natürlich
herunterladen können gtk-xmlviewer
#!/usr/local/bin/ruby -w
# Sample Application for Ruby/GTK -
gtk-xmlviewer.rb
# Beispielprogramm fuer Ruby/GTK
-gtk-xmlviewer.rb
require 'gtk'
require 'nqxml/treeparser'
class XMLViewer <
Gtk::Window
def initialize
super(Gtk::WINDOW_TOPLEVEL)
set_title('Ruby/Gtk
XML Viewer')
set_usize(600,
400)
menubar =
createMenubar
@treeList =
Gtk::Tree.new
@treeList.show
@columnList =
Gtk::CList.new(['Attribut', 'Wert'])
@columnList.show
bottom =
Gtk::HBox.new(false, 0)
bottom.pack_start(@treeList,
true, true, 0)
bottom.pack_start(@columnList,
true, true, 0)
bottom.show
contents =
Gtk::VBox.new(false, 0)
contents.pack_start(menubar,
false, false, 0)
contents.pack_start(bottom,
true, true, 0)
add(contents)
contents.show
signal_connect(Gtk::Widget::SIGNAL_DELETE_EVENT)
{ exit }
end
def createMenubar
menubar =
Gtk::MenuBar.new
fileMenuItem =
Gtk::MenuItem.new("Datei")
fileMenu =
Gtk::Menu.new
openItem =
Gtk::MenuItem.new("Öffnen...")
openItem.signal_connect(Gtk::MenuItem::SIGNAL_ACTIVATE)
{
openDocument
}
openItem.show
fileMenu.add(openItem)
quitItem =
Gtk::MenuItem.new("Beenden")
quitItem.signal_connect(Gtk::MenuItem::SIGNAL_ACTIVATE)
{ exit }
quitItem.show
fileMenu.add(quitItem)
fileMenuItem.set_submenu(fileMenu)
fileMenuItem.show
helpMenuItem =
Gtk::MenuItem.new("Hilfe")
helpMenu =
Gtk::Menu.new
aboutItem =
Gtk::MenuItem.new("Über..")
aboutItem.signal_connect(Gtk::MenuItem::SIGNAL_ACTIVATE)
{
showMessageBox('Über
XMLViewer', 'Ruby/GTK
Beispielprogramm')
}
aboutItem.show
helpMenu.add(aboutItem)
helpMenuItem.set_submenu(helpMenu)
helpMenuItem.show
menubar.append(fileMenuItem)
menubar.append(helpMenuItem)
menubar.show
menubar
end
def selectItem(entity)
@columnList.clear
if
entity.kind_of?(NQXML::NamedAttributes)
keys =
entity.attrs.keys.sort
keys.each
{ |key|
@columnList.append([key,
entity.attrs[key]])
}
end
end
def
populateTreeList(docRootNode,
treeRoot)
entity =
docRootNode.entity
if
entity.instance_of?(NQXML::Tag)
treeItem
= Gtk::TreeItem.new(entity.to_s)
treeRoot.append(treeItem)
if
docRootNode.children.length > 0
subTree
= Gtk::Tree.new
treeItem.set_subtree(subTree)
docRootNode.children.each
do |node|
populateTreeList(node,
subTree)
end
end
treeItem.signal_connect(Gtk::Item::SIGNAL_SELECT)
{
selectItem(entity)
}
treeItem.show
elsif
entity.instance_of?(NQXML::Text)
&&
entity.to_s.strip.length
!= 0
treeItem
= Gtk::TreeItem.new(entity.to_s)
treeRoot.append(treeItem)
treeItem.signal_connect(Gtk::Item::SIGNAL_SELECT)
{
selectItem(entity)
}
treeItem.show
end
end
def
loadDocument(filename)
@document =
nil
begin
@document
=
NQXML::TreeParser.new(File.new(filename)).document
rescue
NQXML::ParserError => ex
showMessageBox("Fehler",
"XML Document kann nicht geparst
werden")
end
if @document
@treeList.children.each
{ |child|
@treeList.remove_child(child)
}
populateTreeList(@document.rootNode,
@treeList)
end
end
def openDocument
dlg
=Gtk::FileSelection.new('Öffnen
Datei')
dlg.ok_button.signal_connect(Gtk::Button::SIGNAL_CLICKED)
{
dlg.hide
filename
=dlg.get_filename
loadDocument(filename)
if filename
}
dlg.cancel_button.signal_connect(Gtk::Button::SIGNAL_CLICKED)
{
dlg.hide
}
dlg.show
end
def showMessageBox(title,
msg)
msgBox =
Gtk::Dialog.new
msgLabel =
Gtk::Label.new(msg)
msgLabel.show
okButton =
Gtk::Button.new('OK')
okButton.show
okButton.signal_connect(Gtk::Button::SIGNAL_CLICKED)
{
msgBox.hide
}
msgBox.set_usize(250,
100)
msgBox.vbox.pack_start(msgLabel)
msgBox.action_area.pack_start(okButton)
msgBox.set_title(title)
msgBox.show
end
end
if $0 == __FILE__
mainWindow =
XMLViewer.new
mainWindow.show
Gtk::main
end
Nachdem Sie den Code
geschrieben oder herunterladen haben,
nehmen wir uns die Zeit, um den Code genau
zu analysieren. Wir beginnen mit der
initialize Methode für die
XML Viewer Klasse. XML Viewer
ist eine Unterklasse von
Gtk::Window und deswegen ist
der erste Schritt, dass man die
Basisklasse, durch den Aufruf der
super Methode, initialisiert
wird. Dabei hat
Gtk::Window.new ein
Singlargument, der auf den Fenstertyp
hinweist; die Grundeinstellung ist
Gtk::WINDOW_TOPLEVEL, aber es
gibt auch andere gültige Werte
Gtk::WINDOW_POPUP und
Gtk::DIALOG. Die nächste zwei
Zeilen legen den Fenstertitel fest und wird
auch die Breite und Höhe initialisiert.
Die nächste Aufgabe ist das
Erzeugen einer Menübalke und Pulldownmenüs.
Wir haben es absichtlich in eine separate
createMenubar hineingesteckt,
um den Code transparenter zu machen. Eine
Erzeugung Menüs in Ruby/Gtk erfordert eine
Erstellung des Gtk::MenuBar
Widgets und dann werden zu dem ein oder
mehrere Gtk::MenuItem Objekte
hinzugefügt. Ein Menüeintrag kann einen
wirklichen Menübefehl darstellen, oder der
kann verwendet werden, um ein Untermenü
oder andere Menü's Enträge, die im
Gtk::Menu Widget enthalten
sind. Dieser Auszug aus der
createMenubar Methode
veranschaulicht uns sozusagen wichtige
Schlüsselpunkte:
fileMenuItem =
Gtk::MenuItem.new("Datei")
fileMenu =
Gtk::Menu.new
openItem =
Gtk::MenuItem.new("Öffnen...")
openItem.signal_connect(Gtk::MenuItem::SIGNAL_ACTIVATE)
{
openDocument
}
openItem.show
fileMenu.add(openItem)
fileMenuItem.set_submenu(fileMenu)
fileMenuItem.show
menubar.append(fileMenuItem)
Der Datei
Menüeintrag(fileMenuItem) ist eine Instanz
vom Gtk::MenuItem Widget,
deren Absicht ist, das Untermenü(fileMenu)
zu zeigen, das noch weitere Menüeinträge
unterbringen kann. Wir rufen eine
set_submenu Methode auf, um
eine Verbindung zwischen
fileMenuItem und
fileMenu herzustellen. Im
Gegensatz zu Datei Menüeintrag
stellt der Offen...
Untermenüeintrag(openItem) einen Befehl für
das Programm dar, wir verbinden den Eintrag
durch das activate Signal mit
openDocument Methode, welche
wir uns später ansehen.
Zurück zur
initialize Methode; wir
erzeugen und zeigen den "tree
list"(Baumstrukturige Liste) Widget:
@treeList =
Gtk::Tree.new
@treeList.show
Und außerdem ein gespalteter
List-Widget:
@columnList =
Gtk::CList.new(['Attribut', 'Wert'])
@columnList.show
Hier haben wir eine
Konstruktion für Gtk::CList
verwendet, welche ein Array mit
Spaltennamen spezifiziert; eine alternative
Konstruktion erlaubt uns eine einfache
Festlegung der Anzahl von Spalten und dann
setzt man ihre Titels später zusammen und
dabei wird die
set_column_title Methode
verwendet. Der gesamte Layout des
Hauptfenster's Widgets wird durch die
Verwendung der horizontalen "packing" Box
dargestellt, die sich ihrerseits in
vertikalen "packing" Box befindet. Die
horizontale "packing"
Box(bottom genannt) enthält
den Gtk::Tree Widget auf der linken Seite
und den Gtk::CList auf der rechten. Die
vertikale "packing"
Box(contents genannt) enthält
eine Menübar, die am oberen Rand liegt und
der Rest des Raumes gehört zur horizontalen
"packing" Box. Dabei ist zu merken, dass
die Arguments von der
pack_start Methode für die
Menübar direkt in der vertikalen "packing"
Box untergebracht sind, um die Ausdehnung
der Menübar zu verhindern, selbst wenn es
einen zusätzlichen Platz gibt:
contents.pack_start(menubar,
false, false, 0)
Die letzte Zeile der
initialize Methode baut einen
"Signalhandler" fürs Hauptfenster selbst
ein. When das Hauptfenster sozusagen
"deleted"(gelöscht) wird(gewöhnlich beim
Klicken des x Buttons in
der oberen rechten Ecke des Fensters), GTK+
wird das abschließen, dabei wird eine
symbolische Konstante für
delete_event
Gtk::Widget::SIGNAL_DELETE_EVENT verwendet.
Es wird dieses Ereignis eingefangen und das
Fenster sofort geschloßen.
Nun tauchen wir weiter in die
nächste Ebene des Programms ein, wir sehen
uns "Signalhandler's" für die Menübefehle
an. Die werden festgesetzt, wenn wir die
Menüeinträge in der
createMenubar Methode
erzeugen. Wir können uns schnell den
Beenden Befehl ansehen,
damit schließt man einfach das
Programm:
quitItem =
Gtk::MenuItem.new("Beenden")
quitItem.signal_connect(Gtk::MenuItem::SIGNAL_ACTIVATE)
{ exit }
Der Über
Menübefehl zeigt eine kleine Dialogbox, die
ein Info über das Programm beinhaltet:
aboutItem =
Gtk::MenuItem.new("Über...")
aboutItem.signal_connect(Gtk::MenuItem::SIGNAL_ACTIVATE)
{
showMessageBox('Über
XMLViewer', 'Ruby/GTK
Beispielprogramm')
}
Hier ist
showMessageBox eine
Hilfemethode für die XMLViewer
Klasse, die eine Dialogbox mit einem
festgelegten Titel und einem
Nachrichtstring zeigt und dazu auch ein
OK Button, welcher die
Dialogbox schließen lässt.
def showMessageBox(title,
msg)
msgBox =
Gtk::Dialog.new
msgLabel =
Gtk::Label.new(msg)
msgLabel.show
okButton =
Gtk::Button.new('OK')
okButton.show
okButton.signal_connect(Gtk::Button::SIGNAL_CLICKED)
{
msgBox.hide
}
msgBox.set_usize(250,
100)
msgBox.vbox.pack_start(msgLabel)
msgBox.action_area.pack_start(okButton)
msgBox.set_title(title)
msgBox.show
end
end
Es gibt viele zweckbedingte
Methoden,die uns mehr Möglichkeiten geben
könnten, die Größe der Box und den Layout
zu kontrolieren, aber dieser einfacher
Vorgang dient unseren Zwecken. Allerdings
die GNOME Bibliothek(aufgebaut auf GTK+)
bietet mehr leistungsfähige und
leichtbediente Klasse für die Erstellung
der Messageboxen und sogennaten
Über Boxen in ihren
Programmen an. Mehr Inforamation über Ruby
Bibliotheken für GNOME findet man auf
Ruby/GTK Homeseite.
Der Menübefehl, der sozusagen
etwas zum Bewegen bringt, wie auch immer,
ist der Offnen Befehl,
welcher die XMLViewer's
openDocument Methode
aufruft:
def openDocument
dlg
=Gtk::FileSelection.new('Öffnen
Datei')
dlg.ok_button.signal_connect(Gtk::Button::SIGNAL_CLICKED)
{
dlg.hide
filename
=dlg.get_filename
loadDocument(filename)
if filename
}
dlg.cancel_button.signal_connect(Gtk::Button::SIGNAL_CLICKED)
{
dlg.hide
}
dlg.show
end
Die new Methode
für Gtk::FileSelection hat ein
String-Argument, der auf den Titelname von
der Dialogbox hinweist. Diese Dialogbox
bietet uns an, irgendwelche Datei
auszuwählen. Dabei ist besonderes
interresant, wie die "clicked" Signale für
beide OK und
Abbrechen in diesem Dialog
abzufangen sind und wie dabei die
ok_button und
cancel_button Zugruffsmethode
vom Gtk::FileSelection Widget zu verwenden
sind. Insbesondere wür den wir gern eine
Datei aufrufen, wir uns ausgesucht haben,
(mit hilfe der get_filename)
und schließlich die Datei zum Gesicht
bekommen könnten. Um das Letzteres zu
machen, brauchen wir die
loadDocument Methode:
def
loadDocument(filename)
@document =
nil
begin
@document
=
NQXML::TreeParser.new(File.new(filename)).document
rescue
NQXML::ParserError => ex
showMessageBox("Fehler",
"XML Document kann nicht geparst
werden")
end
if @document
@treeList.children.each
{ |child|
@treeList.remove_child(child)
}
populateTreeList(@document.rootNode,
@treeList)
end
end
Wenn der XML Parser während der
Erzeugung des NQXML::Document
Objektes eine Ausnahme auslöst, werden wir
wieder unsere showMessageBox
Hilfemethode benutzen. Dadurch wird der
Anwender aufmerksam gemacht und es wird ein
Hinweis auf den Fehler gegeben. Angenommen,
die Datei wurde erfolgreich geladen und man
wird danach den schon existierenden im
Speicher baumstruktiregen Inhalt
losgeworden, und schließlich durch die
populateTreeList Methode
wieder gefüllt.Um den Inhalt der
baumstruktirigen Liste zu aufräumen,
benutzen wir die children
Methode(geerbt vom Gtk::Container), welche
ein Array mit Baum's Elementen zurückgibt.
Dann lassen wir jedes Element mit Hilfe von
"Iterator" durchlaufen und dann löchen wir
jeden Eintrag durch den Aufruf
remove_child Methode.
Die
populateTreeList Methode ruft
sich selbst wiederholend auf, um neuen
baumstrukturigen Ihalt aufzubauen. Der
Prozess des Aufbauens vom Gtk::Tree's
Widget ist ähnlich zum Prozess des
Aufbauens von "Pull-Down" Menü, das wir in
der createMenubar gemacht
haben. Sie können
Gtk::TreeItem Objekte zum
Gtk::Tree Widget hinzufügen
und dann zu diesen Einträgen
Signal-Handler's anhängen, um eine Anzeige
zu bekommen, when diese Elemente selektiert
oder deselektiert, ausgebreitet oder
zusammengeschloßen und etc. werden. Aber
ebenso wieGtk::MenuItem
Objekte Submenüs haben können, die dem
Menüeintrag verbunden sind(stufenähnliche
Pulldown's Menüs), künnen
Gtk::TreeItem Objekte
"Sub-Trees" haben, das ist aber ganz
Anderes, als Gtk::Tree Objekt.
In folgenden Auszug aus der
populateTreeList Methode
verwenden wir diese Konstruktion, um XML
Datei's Knoten zu modellieren:
treeItem =
Gtk::TreeItem.new(entity.to_s)
treeRoot.append(treeItem)
if
docRootNode.children.length > 0
subTree
= Gtk::Tree.new
treeItem.set_subtree(subTree)
docRootNode.children.each
do |node|
populateTreeList(node,
subTree)
end
end
Hier treeItem ist
ein Kind des aktuellen
treeRoot Objektes(whelches
selbst eine Gtk::Tree Instanz ist). When
wir sehen, dass diese XML Entity eine oder
mehrere Entities hat, erstellen wir eine
neue Gtk::Tree Instanz(genannt
subTree) und danach rufen wir
die set_subtree Methode, um
einen Eintrag(treeItem für
diese Instanzen zu setzen.
Jedesmal wenn ein Element im
XML Dokument ausgewählt wird, möchten wir
auch, dass die Attributliste (unseres
Gtk::CList Objekt), das sich in der rechten
Hälfte befindet, neue geladen wird. Um das
zu machen, hängen wir ein Handler zu jedem
Element an. Damit wird durch das Signal
Gtk::Item::SIGNAL_SELECT
unsere selectItem Methode
aufgerufen:
def
selectItem(entity)
@columnList.clear
if
entity.kind_of?(NQXML::NamedAttributes)
keys =
entity.attrs.keys.sort
keys.each
{ |key|
@columnList.append([key,
entity.attrs[key]])
}
end
end
Diese Methode beginnt mit dem
Ausräumen der alten Inhaltsliste und dann
wenn es irgendwelche Attributes gibt,die
mit ausgewählten XML-Entity verbunden sind,
werden sie rechts angezeigt. Und so sieht
unsere Applikation unter Linux aus:
Verwendung Glade GUI Builder
Glade(http://glade.gnome.org)
ist ein GUI Programmtool für GTK+ und
GNOME. Seine Entwickler sind Damon Chaplin
und Martijn van Beers. Sie können sich den
aktuellen Sourcecode für Glade von der
Glade Homeseite herunterladen, aber üblich
ist es, dass bei meisten
Linuxdistributionen Glade schon vorhanden
ist. Dieser Teil ist nicht dazu gedacht,
einen Bericht über Glade zusammenzustellen,
aber es gibt in Internet reichlich
Information über diese Anwendung. Es gibt
auf http::/glade.gnome.org/FAQ Seite eine
einfache Textversion von der Glade FAQ
Liste, außerdem beinhaltet GNOME Version
von Glade Clade FAQ-Liste, Manual und
Quick-Start Guide.
Glade's Projektdatei
ist(*.glade Datei) ist eine XML Datei, die
alle Information über die grafische
Oberfläche beinhaltet. James Henstridge hat
zur Unterstützung eine Bibliothek
entwickelt libglade, welche
uns erlaubt, Glade's Projektdateien
auszulesen und dynamisch in
Echt-Zeit(runtime) die GUI's zu erzeugen.
Das ist wichtig, weil es uns erlaubt, Glade
zu verwenden, um grafische Oberfläche für
die Programmiersprachen, die Glade nicht
direkt unterstützen, zu entwicklen. Die
Homeseite vom libglade Projekt ist
www.daa.com.au/~james/gnome, aber wie
gesagt genauso wie Glade kommt diese
Biblithek bei allen Linuxdistributionen als
Standardoption vor.
Ruby/Libglade ist eine
Erweiterung, die von Avi Bryant entwickelt
war und die bietet uns sozusagen
"Hülle"(wrapper) für libglade
an. Zur Zeit gibt's keine offizielle
Homeseite daür, aber man kann sich die
letzte Version der Erweiterung von RAA
herunterladen. Der Sourcecode enthält unter
anderen Installations- und
Verwendungsanweisungen, sowie auch ein
Beispielprojekt für Testzwecken.
Ruby/LibGlade definiert eine
Single-Klasse GladeXML. Die
new Methode vom
GladeXML hat als Argument den
Dateinamen des Glade-Projektes, und
optional den Namen des Root-Widgets vom
Teil der Benutzeroberfläche, die sie
entwickeln möchten. Wenn Sie doch das ganze
Projekt laden wollen, dann kann man einfach
den zweiten Argument auslassen.
Letzendlich
GladeXML.new erwartet auch
eine in einem "Iterator-Style" Coderblock,
welcher verwendet wird, um Signal-Handler's
Namen mit Ruby Prozeduren oder Methoden zu
verbinden. Während libglade
mit dem Laden einer Information über ihre
Benutzeroberfläche aus der Glade
Projektdatei beginnt, ruft sie einen
"Iterator-Codeblock" auf und zwar für jeden
Handler's Namen, auf den der sozusagen
stößt. Der Codeblock würde dabei einen von
beiden Ruby's Proc oder
Method zurückgeben, die uns
den Code anbietet, um GTK+ Signal zu
handzuhaben. Zum Beispiel eine Version, die
Proc Objekte zurückgibt, würde
so aussehen:
GladeXML.new('myproject.glade') {
|handler_name|
case
handler_name
when
"on_button1_clicked"
proc
{ puts "Goodbye, World!"; exit }
when
"on_button2_clicked"
proc
{ puts "button2 was clicked" }
end
}
Wenn Sie ihren Code so erzeugt
haben,dass die Ruby Methoden, die die
Signale handhaben, die gleiche Namen haben
,wie Handler's Namen, die Sie in Glade
festgelegt haben, und noch sogar saubere
Methode wäre es, Ruby's "Kernel#method"
Methode zu verwenden, damit wird
automatisch zu diesem Handler's Methoden
Verweise definiert:
def
on_button1_clicked
puts "Goodbye,
World!"
exit
end
def on_button2_clicked
puts "button2 was
clicked"
end
GladeXML.new('myproject.glade') {
|handler_name|
method(handler_name)
}
Die GladeXML
Klasse bietet zwei andere Instanz-Methoden
getWidget und
getWidgetByLongName an. Beide
Methode geben zu spezifischen Widgets
Referenzen zurück und beide haben als Input
ein Sing-String Argument. Die
getWidget Methode nimmt für
den Widget einen kleinen Namen(z.B.
"button1"), während
getWidgetByLongName den Namen
hat, welcher auf absoluten Pfad
hinweist(z.B.
"mainWindow.hbox.button1").
Folgende Abbildung zeigt uns
eine Glade's Session mit orinaller
Hello, Linuxuser!
Benutzeroberfäche an. Diese besteht aus
einem Top-Level Fenster und einem Button,
welcher als Kind-Widget fürs Hauptfenster
ist. Wie man schon sieht, haben wir hier
für den Button Signal-Handler
clicked hinzugefügt und haben
ihn als on_button1_clicked
bennant. Die Namen für die Signal-Handler,
die Sie in Glade festlegen, sind die
Verbundgung zwischen Benutzeroberflächen
und Ruby's Code wichtig.
Nach dem Sie das Glade Projekt
in der Datei(myproject.glade) gespeichert
haben, schreiben wir ein kleines
Rubyprogramm, das Ruby/LibGlade
verwendet:
require 'gtk'
require 'lglade'
def on_button1_clicked
puts
"Goodbye Linuxuser!"
exit
end
GladeXML.new('myproject.glade') {
|handler_name|
method(handler_name)
}
Gtk::main
Die ersen zwei Zeilen in diesem
Programm importieren Ruby/GTK und
Ruby/LibGlade Erweiterungen, wo "lglade"
eine Module von Ruby/LibGlade ist. Der
nächste Teil des Programms definiert die
on_button1_clicked Methode,
die wir fürs Button's clicked
Signal verwenden. Als Nächstes erzeugen wir
GladeXML Objekt fürs unsere
myproject.glade Projekt und danach
verbinden wir den Handler's Namen
"on_button1_clicked" mit der passenden
on_button1_clickedHandler's
Methode. Schließlich wie bei Ruby/GTK's
Programmen, beenden wir die Application mit
GTK+ main event loop.