Zurck zur Homepage

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.

Valid XHTML 1.1! Valid CSS!