Einführung in wxRuby
vom
Ursprünglich wollte ich ja Screencasts machen, aber ich habe festgestellt, dass ich nicht imaginär mit jemandem sprechen kann, und Screencasts ohne Erklärungen sind langweilig. Also mache ich stattdessen eine Postserie draus: Wie benutzt man eigentlich das GUI-Toolkit wxRuby?
Einführung
Wie schon beschrieben, handelt es sich bei wxRuby um eines der ach so zahlreichen GUI-Toolkits für Ruby. Ich selbst habe mittlerweile drei von ihnen ausprobiert, angefangen mit Tk, welches unter Windows ein Aussehen wie aus der Steinzeit aufweist (und zwischendurch nicht mal im RubyInstaller enthalten war), fortgeführt mit FxRuby, welches recht kompetent und benutztbar ist, aber leider nicht mehr entwickelt wird, und schließlich eben wxRuby. Diese Artikelserie wird keine Grundlagendiskussion darüber entfachen, welches Toolkit am besten ist; ich gehe einfach mal davon aus, dass, wer das hier liest, sich entweder seine Meinung anhand konkreten Quellcodes machen will oder sich schon für wxRuby entschieden hat und es jetzt lernen will. wxRubys eigene Argumentation kann auf deren Homepage nachgelesen werden.
Ziele und Voraussetzungen
Diese Artikelserie soll die Grundlagen und vielleicht auch einmal die forteschrittenen Gebiete der GUI-Programmierung mit wxRuby behandeln; dementsprechend erwarten die Artikel keine Vorkenntnisse zu wxRuby oder anderen GUI-Toolkits. Ich erwarte lediglich ein grundlegendes Ruby-Wissen, d.h. “Methoden”, “Klassen”, “Vererbung” u.Ä. sollten bekannt und bedienbar sein.
Ferner werde ich ausschließlich auf Ruby 1.9 eingehen. wxRuby lässt sich auch mit Ruby 1.8 verwenden, aber da Ruby 1.8 bald zuende sein wird sehe ich hier keine Zukunft. Außerdem verwende ich seit dem Erscheinen von 1.9.1-p0 ausschließlich Ruby 1.9 und habe noch nie Probleme damit gehabt (Encoding-Exceptions sind nicht ohne Grund da. Das Mischen zweier Strings mit unterschiedlichen Encodings ist eine Ausnahmebedingung!).
wxRuby ist grundsätzlich plattformunabhängig, läuft problemlos auf Microsoft Windows, Linux und auch auf dem Mac, wo bei letzterem aber die 64-Bit-Unterstützung de facto nicht vorhanden ist. Ferner gibt es einen schwerwiegenden Bug in der Graphikbibliothek pixman, der wxRuby (genau wie wxLua und das populäre GNUstep) zum Absturz bringt, welcher leider noch nicht gefixt wurde. Betroffen hiervon ist die pixman-Version 0.22.1, mit der Vorversion 0.20.1 arbeitet es reibungslos. Schließlich gibt es noch ein Ubuntu-spezifisches Problem, was (mal wieder) das Ergebnis der Softwareschnippelei im Debian-Lager ist, die nämlich kurzzeitig wxWidgets nicht mehr als Ganzes kompiliert hat; dieser Bug ist in der aktuellen Entwicklerversion von Ubuntu (Oneiric Ocelot) behoben (wie es um Debian selbst steht, weiß ich leider nicht).
Installation
Das erste, was man in Angriff nehmen sollte, ist die Installation von wxRuby.
# gem install wxruby-ruby19
wxRuby gibt es als zwei Gems: wxruby und wxruby-ruby19; uns interessiert nur das letztere, weil das erstere für Ruby 1.8 kompiliert ist (wer das also noch verwendet, muss wxruby installieren).
wxRubys Dokumentation kommt nicht als RDoc daher, sondern benutzt ein eigenes, Textile-basiertes Format. Sie kann unter http://wxruby.rubyforge.org/doc eingesehen werden und von der RubyForge-Seite als komprimierter Tarball heruntergeladen werden. Ich empfehle dringlichst, das einführende Kapitel der Dokumentation zu lesen, weil die ursprünlich aus den C++-Quellen von wxWidgets selbst generierte Dokumentation (genau wie die Beispiele) unter ihrem C++-Erbe leiden. Die Dokumentation zeigt Methoden wie TextCtrl#set_value, die auch tatsächlich existieren, aber sehr viel rubyischer und schöner als TextCtrl#value= benutzt werden können (nebst anderen Dingen). Auch kommt wxRuby wie gerade schon erwähnt mit einer unüberschaubaren Menge an Beispielcode, welcher im samples/-Verzeichnis des installierten Gems zu finden ist, bei mir etwa /opt/rubies/ruby-1.9.2-p290/lib/ruby/gems/1.9.1/gems/wxruby-ruby19-2.0.1-x86_64-linux/samples. In diesem Ordner ist insbesondere die “Bigdemo” hervorzuheben, die für jedes einzelne Widget ein Beispiel hat (und die alle auf einmal über bigdemo/bigdemo.rb ausgeführt werden können).
Hello, world
So, jetzt wird es aber wirklich Zeit für den ersten Code. Zunächst einmal empfehle ich, wxRuby-bezogenen Quellcode ausschließlich in UTF-8 zu schreiben und Ruby dies mithilfe eines magischen Kommentars
#Encoding: UTF-8
als erste (oder bei Shebang zweite) Zeile mitzuteilen, denn wxRuby erwartet sämtliche Strings in UTF-8, ein manueller Aufruf von String#encode für jedes Textargument wird schnell lästig.
wxRuby wird mithilfe der Zeile
require "wx"
eingebunden, was das Namespace-Modul Wx
einführt, unter dem sich alle
wxRuby-Klassen befinden. Jede wxRuby-Anwendung leitet sich nun von
wxRubys App
-Klasse ab:
#Encoding: UTF-8 require "wx" class HelloApp < Wx::App end
Diese App
-Klasse bietet drei sogenannte Hook-Methoden an, d.h.
Methoden, die bei bestimmten Ereignissen aufgerufen werden. Es handelt
sich dabei um App#on_init
, App#on_run
und App#on_exit
, welche
respektive bei GUI-Start, Mainloop-Durchlauf
(d.h. einmal pro Durchlauf im Main/Eventloop) und GUI-Beenden aufgerufen werden. Von allen dreien muss
mur #on_init
überschrieben werden, da es dafür zuständig ist, das
Hauptfenster zu erstellen.
#Encoding: UTF-8 require "wx" class HelloApp < Wx::App def on_init end end
Fenster werden in wxRuby mithilfe der Klasse Frame
dargestellt. Möchte
man ein neues Fenster erstellen, empfiehlt sich logischerweise der
Aufruf von Frame.new
, dessen lange Parameterliste in der
Dokumentation gelesen werden kann und die überaus abschreckend
aussieht:
Frame.new(Window parent, Integer id, String title,
Point pos = DEFAULT_POSITION,
Size size = DEFAULT_SIZE,
Integer style = DEFAULT_FRAME_STYLE,
String name = "frame")
Das ist aber nur halb so schlimm, denn wxRuby bietet die Möglichkeit,
statt dieser Langen aus C++ geerbten Parameterkette ein Hash-Argument
für alle Parameter außer parent
zu verwenden, wodurch die Angabe
vieler unnötiger Parameter entfällt und man auch in der Reihenfolge
freier ist. Wollen wir also das Hauptfenster mit der Größe 400×400 Pixel
und einem begrüßenden Titeltext erstellen, so geht man wie folgt vor:
#Encoding: UTF-8 require "wx" class HelloApp < Wx::App def on_init @mainwindow = Wx::Frame.new(nil, size: [400, 400], title: "Hello, World!") end end
Man beachte die folgenden Dinge an obigem Quelltext:
- 1.9er Hash-Syntax: So sehen benannte Parameter einfach besser aus.
- Größenangabe als Array: Man kann zwar explizit die
Wx::Size
-Klasse verwenden, aber ein kleines Array schreibt sich eleganter und lesbarer. -
nil
als Angabe des Elternfensters: Das Hauptfenster hat kein Elternfenster, also kann man auch keins übergeben. Weilparent
aber (meistens) der einzige Paramter ist, der verpflichtend ist und nicht über die Hash-Syntax angegeben werden kann, übergebe ich einfachnil
.
Schön, nun haben wir also ein Fenster erstellt, aber das Backen eines Kuchens stellt ihn auch noch nicht automatisch auf den Tisch. Wir müssen unser Fenster noch anzeigen:
#Encoding: UTF-8 require "wx" class HelloApp < Wx::App def on_init @mainwindow = Wx::Frame.new(nil, size: [400, 400], title: "Hello, World!") @mainwindow.show end end
Das sollte relativ eindeutig sein. Wieder “verstecken” kann man ein Fenster mithilfe der Methode #hide (was aber eher unüblich ist).
Nachdem diese atemberaubende Klasse nun erstellt ist, wird es Zeit, sie
zu instanziieren, damit wir auch etwas sehen. Mit HelloApp.new
erstellen wir die gesamte GUI, und mit
#main_loop starten wir den Eventloop, übergeben also die Kontrolle an
wxRuby und warten an die von wxRuby versendeten Ereignisse (dazu in
einer späteren Folge mehr). Unvollständiger Pseudocode für diese Methode
sähe so aus:
#..irgendwo in wxRubys Code... def main_loop on_init loop do #Process events... on_run break if @events.first.kind_of?(QuitEvent) end on_exit end
In Wahrheit ist der Code noch etwas komplizierter und außerdem in C++ geschrieben, aber man erkennt das grundlegende Prinzip und wie die drei oben angesprochenen Hook-Methoden aufgerufen werden.
Der finale Code der Hello-World-Anwendung schaut dann so aus:
#Encoding: UTF-8 require "wx" class HelloApp < Wx::App def on_init @mainwindow = Wx::Frame.new(nil, size: [400, 400], title: "Hello, World!") @mainwindow.show end end HelloApp.new.main_loop
Damit beende ich den einführenden Teil dieser Serie und werde mich im Nächsten mit der Verwendung von Widgets/Controls und dem empfohlenen Layout von wxRuby-Programmen auseinandersetzen.
Valete.