Container und Signale

Motif, Tk und Qt sind bereits seit längerem mit Perl zu benutzen. Mit Hilfe des passenden Moduls kann man auch das Gnome-Toolkit Gtk von Perl aus einsetzen.

In Pocket speichern vorlesen Druckansicht 1 Kommentar lesen
Lesezeit: 10 Min.
Von
  • Oliver Fischer
  • Martin Wehner
Inhaltsverzeichnis

Der Durchbruch als Sprache für grafische Applikationen ist Perl im Gegensatz zu anderen Skriptsprachen wie Tcl verwehrt geblieben, da es nicht mit einem Standard-Toolkit ausgestattet ist. Anbindungen an die üblichen Toolkits sind jedoch vorhanden, und seit neuestem schickt sich nun neben Tk, Qt und anderen Toolkits das Gimp-Toolkit - kurz Gtk - an, diesen noch leeren Platz auszufüllen. Dabei beschränken sich die Entwickler nicht nur auf die Bereitstellung der einzelnen Widgets, sondern ermöglichen auch den Zugriff auf die Gnome-Umgebung und OpenGL durch die Unterstützung von GtkGLArea.

Um auf den aktuellsten Stand des Moduls Zugriff zu haben, sollte man es sich vom FTP-Server des Gnome-Projekts besorgen und selbst kompilieren. Einzige Voraussetzung hierfür ist eine Installation des Gtk-Toolkits mit den Header-Dateien und dem Programm gtk-config.

Alle weiteren Informationen bestimmt der Aufruf von perl Makefile.PL automatisch. Das gilt zum Beispiel für Gnome: Sind dessen Bibliotheken installiert, enthält der entstehende makefile Unterstützung für den Desktop. Abschließend übersetzt man mit make die Quellen und installiert das Modul mit make install.

Obwohl das zugrunde liegende Gtk-Toolkit in C geschrieben ist, sind alle Widgets als Objekte realisiert. Dieser Ansatz ermöglicht es, die Vorteile der Objektorientierung mit der Integrationsfähigkeit von C-Bibliotheken in andere Sprachen zu verbinden. Deutlich tritt dieses OO-Konzept bei GtkPerl hervor, dessen Klassen die einzelnen Widgets repräsentieren.

Wie bei vielen OpenSource-Projekten ist die Dokumentation ein entscheidender Schwachpunkt. Lediglich eine während der Kompilierung erzeugte minimale Beschreibung der einzelnen Klassen und deren Methoden im POD-Format findet sich im Verzeichnis build. Allerdings enthält sie nur die für die jeweiligen Klassen definierten Methoden, nicht die übergeordneter Klassen. Alle weiteren Informationen muss man sich aus der Dokumentation zum Gtk, Mailinglisten und den mitgelieferten Beispielen zusammensuchen. Als Einstieg in die Gtk-Programmierung empfehlen sich das auf dem Webserver angebotene Tutorial und die mitgelieferten Beispiele im Verzeichnis Gtk/samples der Distribution.

Etwas leichter gestaltet sich die Einarbeitung, weil GtkPerl sich weitgehend an den Parametern der C-Funktionen von Gtk orientiert, allerdings bei der Benennung der Objekte davon abweicht. So heißt zum Beispiel das Gtk-Widget gtk_button in Perl Gtk::Button und für die C-Funktion gtk_button_new_with_label() ist Gtk::Button->new_with_label() zu schreiben. Da dieses einfache Schema für alle Gtk-Objekte gilt, fällt das Übertragen von C-Code nicht schwer. Analog ist die Umsetzung von Gdk-Objekten und -Methoden definiert, die einen vereinfachten Zugriff auf die Funktionen der Xlib bieten. Allerdings liegen dessen Klassen in der Modulhierarchie unterhalb von Gtk::.

Programme mit grafischen Oberflächen werden in der Regel durch Ereignisse, so genannte Events, gesteuert. Meistens löst der Anwender diese Events aus, beispielsweise indem er die Maus bewegt oder eine Taste drückt, und das Fenstersystem leitet sie an das Toolkit weiter. Dieses sorgt dafür, dass Ereignisse diejenigen Widgets erreichen, die sich dafür interessieren: Einen Mausklick schickt es beispielsweise an den Knopf, über dem sich die Maus gerade befindet.

Für die Behandlung von Ereignissen hat das Gtk die Idee der Signale von Qt übernommen. Trifft ein Ereignis bei einem Gtk-Widget ein, sendet es ein Signal aus, worauf die diesem Signal zugeordneten Callbacks aufgerufen werden. Welche Events ein Widget verarbeiten kann, hängt zum einen von seinem Typ ab, zum anderen erbt es alle Event-Fähigkeiten seiner übergeordneten Klassen.

Wie Listing 1 demonstriert, stellt GtkPerl für die Verbindung von Events und Callbacks die Methode signal_connect() zur Verfügung. Als ersten Parameter erwartet sie den Namen des Signals, für das sie die Callback-Funktion registrieren soll. Der zweite Parameter ist eine Referenz auf diese Funktion, die man entweder als anonyme Subroutine in Form von sub { ...} erzeugen oder mittels \&funktion übergeben kann.

Mehr Infos

Listing 1

 1 #!/usr/bin/perl
2
3 use Gtk;
4
5 $| = 1;
6
7 Gtk->init();
8
9 $window = Gtk::Window->new(qw(toplevel));
10 $window->signal_connect('destroy', sub { Gtk->exit(0) });
11 $window->signal_connect('delete_event', sub { $window->destroy() });
12 $button = Gtk::Button->new(q(Hello World));
13 $button->signal_connect('clicked', \&hello);
14 $window->add($button);
15 $window->show_all();
16
17 Gtk->main();
18
19 sub hello { print "Hello World\n" }

Dies sollte man niemals mit \&function() verwechseln, es sei denn, man benötigt eine Referenz auf den Rückgabewert der Funktion. Alle weiteren Parameter für signal_connect() bekommt die Callback-Funktion wie in Perl üblich im Array @_ übergeben. Dessen erstes Element ist immer eine Referenz auf das Objekt, das das Signal ausgesandt hat.

Grafische Oberflächen bestehen meistens aus vielen einzelnen Elementen, die ‘irgendwie’ angeordnet sind. Dafür stellt das Gtk so genannte Container-Widgets bereit. Sie sind indirekt von der Klasse Gtk::Container abgeleitet und erben von ihr die Fähigkeit, andere Widgets aufzunehmen. Die Flexibilität dieses Mechanismus reicht beispielsweise so weit, dass man einen Button erzeugen kann, der eine Liste mit Buttons enthält. Ob das sinnvoll ist, sei dahingestellt.

Zusätzlich bietet GtkPerl Widgets an, die direkt zur Verwendung als Container vorgesehen sind. So stehen neben einem generischen Window unter anderem Panes, Rahmen und Boxen zur Verfügung, von denen es in den meisten Fällen eine Variante für die horizontale und vertikale Anordnung gibt. Diese unterscheiden sich nicht nur in der möglichen Zahl ihrer Kinder, sondern auch in ihren Methoden und dem Verhalten bei Layoutänderungen. Den vielen Worten soll jetzt ein beispielhaftes ‘Hello World’-Programm (Listing 1) folgen. Nachdem es das Gtk-Modul geladen hat, initialisiert es in Zeile 7 das Toolkit, anschließend erzeugt das Skript in Zeile 9 ein Toplevel-Fenster. Die Methode signal_connect() in den nächsten beiden Zeilen übernimmt das Setzen der Callbacks für die Signale delete_event und destroy des Toplevel-Fensters. delete_event ist die Aufforderung des Fenstermanagers zum Schließen des Fensters und löst über den gesetzten Callback den Aufruf der Methode destroy() zur Zerstörung des Fensters aus. Diese Methode ihrerseits sendet das Signal destroy, das Gtk->exit(0) in Zeile 10 durch Beendigung des Skripts verarbeitet.

In den Zeilen 12 bis 15 entsteht ein Knopf im Fenster. Da zunächst alle Widgets unsichtbar sind, muss man sie entweder einzeln mit der Methode show() anzeigen oder diese Aufgabe einem übergeordneten Widget wie in Zeile 17 überlassen, wo die Methode show_all() das Fenster und alle ihm untergeordneten Widgets darstellt. Ist die gesamte Vorbereitung abgeschlossen, übergibt Gtk->main() die Programmkontrolle an die Event-Verarbeitung des Toolkits.

Zurück zur Anordnung von Widgets. Das Skript in Listing 1 benutzt Gtk::Window als Container für einen Knopf. Da man meistens mehr als ein Widget verwenden möchte, Gtk::Window aber nur eines aufnehmen kann, bietet GtkPerl neben vielen anderen Container-Widgets so genannte Boxen an. Sie können eine beliebige Anzahl von Kindern horizontal beziehungsweise vertikal anordnen.

In Listing 2 erstellt das Skript nach der Erzeugung des Toplevel-Fensters in Zeile 11 eine Gtk::VBox und hängt sie eine Zeile später in das Fenster ein. Das Verhalten bei Größenänderungen von Boxen bestimmen wie bei den meisten anderen Widgets die Parameter der Methoden zum Einhängen und des Konstruktors, hier also new(). So weist dessen erster Parameter beispielsweise das Gtk::VBox-Widget an, allen Kindern dieselbe Höhe zuzuweisen, während der zweite Parameter den vertikalen Abstand zwischen ihnen bestimmt. Die Methode pack_start() setzt den ersten Button am oberen Ende der Box ein, pack_end() hingegen den zweiten Button am unteren Ende. Ersetzt man Zeile 11 gegen $box = Gtk::HBox->new(0, 0), demonstriert das Programm eine horizontale Box.

Mehr Infos

Listing 2

 1  #!/usr/bin/perl
2
3 use Gtk;
4
5 Gtk->init();
6
7 $window = Gtk::Window->new(qw(toplevel));
8 $window->signal_connect('destroy', sub { Gtk->main_quit() });
9 $window->signal_connect('delete_event', sub { $window->destroy() });
10
11 $box = Gtk::VBox->new(0, 0);
12 $window->add($box);
13
14 $b1 = Gtk::Button->new_with_label(q(1. Button));
15 $box->pack_start($b1, 0, 1, 0);
16 $b2 = Gtk::Button->new_with_label(q(2. Button));
17 $box->pack_end($b2, 0, 1, 0);
18
19 $window->show_all();
20
21 Gtk->main();
22
23 print "Programmende\n";

Mit der Methode Gtk->main() übergibt jedes GtkPerl-Programm die Programmsteuerung an die Gtk-Mainloop - auch Eventloop genannt - und wartet auf Events, die es an die Widgets weiterleitet. Mit zwei Funktionen kann man diese Mainloop verlassen: Gtk->exit(0) wie in Listing 1 veranlasst ein sofortiges Ende des Programms mit dem übergebenen Parameter als Rückgabewert. Da man die Mainloop rekursiv aufrufen kann, steht die Methode Gtk->main_quit() zur Verfügung, die nur die aktuelle Mainloop beendet. Dies nutzt das Skript in Listing 2, um zum Programmende noch Aktionen auszuführen.

Listing 3 demonstriert geschachtelte Mainloops in Verbindung mit einem modalen Fenster. Die Callback-Funktion ab Zeile 22 erzeugt ein neues Toplevel-Fenster, das mit der Methode set_modal() modal wird. Dadurch darf der Anwender nur in diesem Fenster Eingaben vornehmen. Diese Vorgehensweise bietet sich beispielsweise für Dialoge an, die notwendige Daten abfragen, und deren Rückgabewert für den weiteren Programmablauf erforderlich ist. Nach dem Schließen des modalen Dialogs kann der Anwender mit den anderen Fenstern normal weiterarbeiten.

Mehr Infos

Listing 3

 1  #!/usr/bin/perl
2
3 use Gtk;
4
5 $| = 1;
6
7 Gtk->init();
8
9 $window = Gtk::Window->new(qw(toplevel));
10 $window->signal_connect('destroy', sub { Gtk->exit(0) });
11 $window->signal_connect('delete_event', sub { $window->destroy() });
12 $window->set_usize(200, 200);
13
14 $button = Gtk::Button->new_with_label(q(Klicken!));
15 $button->signal_connect('clicked', \&modal_window);
16 $window->add($button);
17 $window->show_all();
19
20 Gtk->main();
21
22 sub modal_window {
23 $modalwindow = Gtk::Window->new(qw(toplevel));
24 $modalwindow->set_usize(100, 100);
25 $modalwindow->set_modal(1);
26 $modalwindow->signal_connect('destroy',
27 \&close_modal_window, \$mw);
28 $modalwindow->signal_connect('delete_event',
29 sub { $modalwindow->destroy() });
30 $modalwindow->show();
31
32 print "\tRekursiver Mainloop: Start\n";
33 Gtk->main();
34 print "\tRekursiver Mainloop: Ende\n";
35 }
36
37 sub close_modal_window {
38 my ($widget, $window) = @_;
39
40 $$window = undef;
41 Gtk->main_quit();
42 }

Ist es außerdem notwendig, mit der weiteren Verarbeitung zu warten, bis alle Eingaben oder Aktionen in dem betreffenden Fenster beendet sind, kann man wie in Zeile 33 eine weitere Mainloop starten. Die print()-Anweisungen demonstrieren dieses Verhalten. Ist hingegen diese Zeile auskommentiert, sind zwar nur Aktionen im modalen Fenster möglich, das eigentliche Programm arbeitet aber weiter. Ab Zeile 37 verlässt die Callback-Funktion close_modal_window die Mainloop mit Gtk->main_quit(), die Applikation läuft weiter.

Mit GtkPerl steht Perl-Programmierern neben Tk und Qt ein weiteres Toolkit zur Verfügung, dessen Einsatz allerdings momentan noch auf Unix-Systeme begrenzt ist. Da aber Gtk selbst schon nach Win32 portiert wurde, sollte es nur eine Frage der Zeit sein, bis GtkPerl plattformübergreifend nutzbar ist.

Oliver Fischer
ist bei der SPM GmbH als Datenbankentwickler beschäftigt.

Martin Wehner
ist Student der Medieninformatik und nebenbei bei der SPM GmbH tätig.

[1] http://developer.gnome.org/doc/API/gtk/

[2] http://developer.gnome.org/doc/API/gdk/

[3] Rolf Herzog; Signalgeber; Widget-Bibliothek Gtk (ck)