QVINTVS · SCRIBET

Einführung in wxRuby

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:

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.