Ein 'Hello-World!' in Gtk+ [Vortrag]

(c) 2000 Marc Lehmann <pcg@goof.com>
1 April 2000


Table of Contents


Abstract

Gtk+ (das GNU ToolKit) ist ein relativ junges UI-ToolKit mit einem objektorientierten Design. Gtk+ selbst ist in C geschrieben, es existieren jedoch Bindungen für C++, Perl, Python und einige andere Sprachen. In diesem Vortrag wird Schritt für Schritt ein einfaches "Hello-World!"-Programm erzeugt, um das "look&feel" von Gtk+ zu erfahren


1. Gtk+

Gtk+ (das GNU ToolKit) ist ein relativ junges UI-ToolKit mit einem objektorientiertem Design. Gtk+ selbst ist in C geschrieben, es existieren jedoch Bindings für C++, Perl, Python und einige andere Sprachen. Das Perl-Interface ist noch nicht vollkommen ausgereift, ist aber schon hervorragend benutzbar und wird immer mehr eingesetzt (z.B. für Administrationsfrontends in Debian GNU/Linux). Die Vorteile von Gtk+ gegenüber Tk und anderen Toolkits ist die hervorragende Integration: Perl-Widgets können von anderen Sprachen benutzt werden (z.B. von C) und natürlich umgekehrt. Eigene Widgets können sehr einfach erzeugt werden (im Gegensatz zu Tk), und die Gtk+-Bibliothek ist frei (im Sinne der LGPL) -- auch auf anderen Betriebssystemen als UNIX.


2. Gtk

Gtk ist das Perl-Modul, das die Schnittstelle zur Gtk+-Bibliothek bildet. Da die meisten Menschen "Gtk" sagen, wenn sie Gtk+ meinen, wird die Perl-Schnittstelle meistens explizit "Gtk-Perl" genannt (genauso, wie das Gimp-Modul meistens Gimp-Perl genannt wird).

Zur Zeit bietet das Gtk-Modul auch Bindungen für Gnome und einige andere Bibliotheken an, möglicherweise (das ist nicht sicher) werden diese Module aber aufgetrennt werden: Gtk+ war ursprünglich das Gimp-ToolKit, und sowohl Gtk+ als auch Gimp sind kein Teil des Gnome-Projektes und können deshalb ohne Gnome verwendet werden.

2.1. Ein einfaches Programm

Hier ist ein ganz einfaches Gtk-Programm. Nichtsdestotrotz öffnet es schon ein Fenster:

use Gtk;

Gtk->init;

$main = new Gtk::Window;

$main->show;
Gtk->main;

use Gtk; sollte klar sein. Eine Eigenheit von Gtk+ ist es, daß es zuerst explizit initialisiert werden muß (in einem Gnome-Programm würde statt Gtk->init ein Gnome->init stehen), sonst gibt es jede Menge Fehler.

Der Aufruf new Gtk::Window ist ein Weg, ein neues Fenster zu erzeugen. Neue Gtk-Widgets sind Anfangs aber noch unsichtbar. Damit sie angezeigt werden, muß zuerst die show-Methode aufgerufen werden (es gibt auch das entsprechende hide).

Das letzte, was unser Programm macht, ist, in die Hauptschleife von Gtk+ zu springen. Die Hauptschleife zeigt das Fenster und und -- wartet auf Ereignisse:

Das Fenster ist natürlich leer und es wurden auch keine Reaktionen auf Ereignisse. Die einzige Möglichkeit, das Programm zu beenden, ist es, es zu "killen" (entweder xkill oder z.B. SIGINT).

2.2. Das Signalsystem von Gtk+

Gtk+ setzt intern auf einer Bibliothek namens Gdk (GNU Drawing ToolKit) auf, die eine Schnittstelle auf niedriger Ebene zum jeweiligen Fenstersystem (X, Win32, BeOS...) bildet. Diese Schnittstelle erzeugt Ereignisobjekte und reicht sie an Gtk+ weiter.

Gtk+ verteilt diese Ereignisse an seine Widgets, indem es diesen Signale schickt. Ein Signal ist z.B. eine Mausklick (clicked) oder das anklicken des "Fenster-Schließen"-Knopfes (delete_event). Gtk+ kennt aber auch eigene Signal, z.B. wenn ein Objekt zerstört wird (destroy), was einem Destruktor entspricht.

Jeder Signal-Handler kann das Signal konsumieren, an andere Widgets weiterreichen oder einfach ignorieren (wodurch es z.B. an das Vaterwidget gelangt).

Um eine Aktion an ein Signal zu binden, kann man die signal_connect-Methode aufrufen:

$main = new Gtk::Window;

$main->signal_connect(delete_event => sub  Gtk->main_quit );

Nun kann man den Schließknopf des Fensters betätigen, und das ausgelöste Signal (delete_event) führt zu einem Aufruf von Gtk->main_quit. Das Beendet die Hauptschleife und damit das Programm.

2.3. Der erste Button

Nun zu etwas mehr Aktion. Ein "Hello-World!"-Button muß her. Dies ist ganz einfach:

$button = new Gtk::Button "Hello, World!";
$main->add($button);

$button->show;

Das einzig neue (neben der Erkenntnis, daß es auch Gtk::Buttons gibt), ist der Aufruf von $main-add>. Gtk::Window ist ein Gtk::Container, und erbt von diesem einige Methoden (unter anderem add).

Das Fenster sieht nun so aus (nicht schön, aber es wird):

Nun besetzen wir noch das clicked-Ereignis mit einem überflüssigen Signal-Handler:

$button->signal_connect(clicked => sub  print "I was here!\n" );

2.4. Vereinfachungen

Das ganze Programm kann man (wenn man will) noch etwas einfacher haben. Zuersteinmal: mir geht dieses dauernde $xxx->show auf die Nerven. Weil es anderen wohl auch so ging, gibt es die Methode show_all. Sie ruft show für das Widget und, rekursiv, für alle seine Kinder auf. Man kann also alle (naja, zwei) show-Aufrufe durch ein einzelnes $main->show_all ersetzen.

Die zweite Vereinfachung ist eher Geschmackssache: Man kann die Erzeugung des Widgets und alle Initialisierungen (z.B. Signale) in einem Aufruf erledigen:

use Gtk;

init Gtk;

$main = new Gtk::Widget "Gtk::Window",
            -signal::delete_event => sub  Gtk->main_quit ;

add $main (new Gtk::Widget "Gtk::Button",
               -label => "Hello, World!",
               -signal::clicked => sub  print "I was here!\n" );

$main->show_all;

Gtk->main;

2.5. Verschönerungen

Um noch ein paar Features vorzustellen sollen noch ein paar Knöpfe hinzukommen. Der "Hello-World"-Knopf soll in einen Rahmen. Unter den Rahmen sollen zwei Knöpfe: der erste soll den Text an- und abschalten, der zweite soll das Programm beenden:

Die Technik, Widgets an- und abzuschalten wird gerne für Dialoge in der Art "Advanced Options >>" verwendet, um dem Benutzer mehr Optionen anzubieten.

Gtk+ bietet viele Arten von Layout-Managern. Grundsätzlich möchte es aber das Layouten des Fensters gerne selbst übernehmen. Man kann die Größe und Lage von Widgets selbst bestimmen, aber Gtk+ arbeitet wesentlich besser, wenn man nur Hinweise gibt. Ein solcher Hinweis kann in Form der Gtk::VBox und Gtk::HBox-Container geschehen. Diese sind Rechtecke, die mehrere Widgets enthalten können, die vertikal oder horizontal ausgerichtet werden.

Das Hauptelement des Fensters soll also eine Gtk::VBox werden. Oben soll eine Gtk::Frame stehen, und im unteren Teil soll eine Gtk::HBox mit den "Bedienelementen" sein (Eine Gtk::Box kann durchaus auch mehr als zwei Widgets enthalten). Die VBox wird einfach erzeugt mit:

add $main (my $vbox = new Gtk::Widget "Gtk::VBox",
                      homogeneous => 0,
                      spacing => 5,
                      border_width => 5);

Eine Gtk::Box ist homogeneous, wenn alle Widgets die gleiche Größe haben. Unsere VBox soll das nicht sein, die Gtk::HBox dagegen schon. Dann wird eine Gtk::Frame erzeugt, in die das Gtk::Label mit dem "Hello, World!" gepackt wird (der Layoutvorgang wird auch "packen" genannt, da die Widgets dabei in den verfügbaren Raum "eingepackt werden").

# Message-Frame:
add $vbox (my $frame = new Gtk::Frame "A Message:");

# Message-Label:
add $frame (my $label = new Gtk::Label "Hello, World!");

Die horizontale Box für die Knöpfe wird mit einem vereinfachten Konstruktor (statt dem allgemeinen Gtk::Widget->new) erzeugt, der nur zwei Argumente ("homogen" und "Abstand") akzeptiert.

add $vbox (my $hbox = new Gtk::HBox 1,5);

Das Interessanteste an dem Programm ist zweifellos der Umschaltknopf, der die Nachricht an- und abschaltet. Dazu bedienen wir uns eines Gtk::ToggleButtons, der zwei Zustände (aktiv und inaktiv) besitzt:

add $hbox (new Gtk::Widget "Gtk::ToggleButton",
           label => "Message Shown",
           active => 1,
           signal::clicked => sub 
              $label->visible
                 ? $label->hide
                 : $label->show;
           );

Hier ist das gesamte Programm:

use Gtk;

init Gtk;

$main = new Gtk::Widget "Gtk::Window",
            -signal::delete_event => sub  Gtk->main_quit ;

add $main (my $vbox = new Gtk::Widget "Gtk::VBox",
                      homogeneous => 0,
                      spacing => 5,
                      border_width => 5);

# Message-Frame:
add $vbox (my $frame = new Gtk::Frame "A Message:");

# Message-Label:
add $frame (my $label = new Gtk::Label "Hello, World!");

# Button-Bar
add $vbox (my $hbox = new Gtk::HBox 1,5);

# Toggle-Button
add $hbox (new Gtk::Widget "Gtk::ToggleButton",
           label => "Message Shown",
           active => 1,
           signal::clicked => sub 
              $label->visible
                 ? $label->hide
                 : $label->show;
           );

# Close Button
add $hbox (new Gtk::Widget "Gtk::Button",
           label => "OK",
           signal::clicked => sub  main_quit Gtk );

$main->show_all;

Gtk->main;

3. Eine Widget-Demo

Als Abschluß folgt noch ein Screenshot, bei dem ich mich bemüht habe, die wichtigsten (schon verfügbaren) Widgets alle auf einen Bildschirm zu bekommen (das Gtk-Perl-Demoprogramm, Gtk/samples/test.pl war sehr hilfreich ;)

Natürlich kann man jederzeit auch eigene Widgets programmieren...