GUI-Programme erstellen mit Glade

Aus LinuxUser 02/2002

GUI-Programme erstellen mit Glade

Lichtung im GTK-Wald

Die Entwicklung auf GTK+ basierender, grafischer Benutzeroberflächen – ein schwieriges, umständliches Unterfangen? Der GUI-Builder Glade widerlegt solche Behauptungen.

Kaum hat man seine erste Programmierstunde absolviert und in der Welt der Schleifen und Funktionen Fuß gefasst, beginnen einen neue Fragen zu quälen: “Wie werden beispielsweise diese schönen, bunten Applikationen entwickelt?” Sogleich erfährt man, dass Ansätze wie OO-Entwicklung zur Tagesordnung in diesem Gebiet gehören. Beängstigt betritt man das neue Terrain und vernimmt zunächst, was ein API (“Application Programmers’ Interface”) ist: Eine Sammlung von Klassen in Form von Bibliotheken, mit denen man endlich anfangen kann, grafische Bedieneroberfläche zu entwickeln… Muss dies alles so kompliziert sein?

Wichtige Konzepte

Nicht unbedingt. Vor allem aber muss man nicht alles auf einmal lernen, um die ersten Schritte bei der Programmierung grafischer Benutzerschnittstellen zu gehen. Manche Kenntnisse sind allerdings unerlässlich zum richtigen Programmentwickeln, zum Beispiel das Verständnis von Konzepten wie Objekt, Klasse, Signal, Ereignis etc. Anderes – etwa die Kenntnisse der API-Funktionen – kann man sich aber anhand von Beispielen im Verlauf der Entwicklung aneignen.

Die Strukturen in einer Bedienoberfläche werden als Objekte behandelt, also als Gruppierung von Daten und speziellen Funktionen (in der Welt der objektorientierten Programmierung Methoden genannt) zu einer individuellen, identifizierbaren, logischen Einheit. Solche Einheiten (etwa die Knöpfe eines Fensters) können wiederholt verwendet werden, und obwohl sie normalerweise über die gleichen Methoden verfügen, wirken sie auf unterschiedliche Datenbestände. Das erklärt zum Beispiel, warum beim Anklicken (Methode) eines Knopfes verschiedene Aufgaben (Datenverkehr) erledigt werden können.

Der Aufbau eines Objekts wird in einer Klasse beschrieben. Deswegen kann man sagen, dass Klassen als Schablonen für Objekte dienen. Neue Klassen können auf bereits vorhandenen aufbauen und so erweitert werden, dass sie neue Aufgaben bewältigen.

Um ein Programm zu befähigen, auf vom Benutzer hervorgerufene Ereignisse entsprechend zu reagieren, werden in der GUI-Programmierung Signale verwendet. Sie kommen zum Zuge, wenn ein Mausknopf gedrückt, ein Fenster zugemacht oder Text getippt wird. Bei jedem Ereignis sendet das entsprechende Objekt je nach Typ und Klasse ein unverwechselbares Signal (das heißt, eine seiner Methoden wird aufgerufen). Es lässt sich von einem Signal-Handler regelrecht aufnehmen, wobei eine bestimmte Aufgabe erfüllt wird.

Das Gimp-Toolkit

Eine der bekanntesten Linux-APIs zur GUI-Entwicklung ist das Gimp-Toolkit GTK+. Es bildet eine Hierarchie von Objekten (hier Widgets genannt), die über die verschiedensten Signale verfügen. Es gibt Signale, die alle Widgets von ihren “Ausgangsklassen” erben (zum Beispiel destroy) und andere, Widget-spezifische (etwa das Signal toggled von den “Ein-/Aus-Schaltern”). Programme mit GTK+ zu entwickeln ist nicht schwierig, verlangt aber, dass man mit dessen Widgets und Methoden vertraut ist.

GTK+ benutzt eine sehr flexible Methode zur Positionierung der Widgets auf der Bedieneroberfläche: das Verpacken einzelner Widgets in Containern. Mit ihnen informiert man GTK+, an welcher Position ein Widget zu erscheinen hat, und bereitet Zellen vor, in denen andere Widgets platziert werden. Die wichtigsten Container sind Boxen und Tabellen.

Den Aufbau eines GUIs aus diesen Widgets und Containern in einem Text-Editor zu kodieren, erfordert tatsächlich einige Erfahrung. Einfacher geht es durch “visuelle Programmierung”, bei der man das Interface einer Applikation nach dem WYSIWYG-Paradigma mit einem Minimum an Aufwand gestalten kann. Das entlastet den Programmierer, sodass sich dieser vornehmlich auf die Programmierung der Funktionalität konzentrieren kann.

Ein Tool, das GTK-Programmierer mit diesem “Rapid Application Development”-(RAD-)Ansatz unterstützt, ist Glade, zu Deutsch “Lichtung”. Dieses Programm ist in der Lage, GUI-Code in verschiedenen Programmiersprachen zu erstellen (C, C++, Ada95, Eiffel, Perl und Python) und steht unter [1] zum Download bereit, sofern es nicht ohnehin mit der Distribution mitgeliefert wird. Ein Kompilat für Windows kann unter [2] bezogen werden.

Vorausgesetzt, dass GTK+ in der Version 1.2.0 (oder höher), autoconf in der Version 2.13 und automake in der Version 1.4 vorhanden sind, erfolgt die Installation von Glade aus dem Quellcode heraus ganz einfach mit den üblichen Kommandos: ./configure =Applikationsverzeichnis, make und make install. autoconf und automake sind auch notwendig, um mit Glade erstellte Projekte erfolgreich zu übersetzen. Falls GNOME-Unterstützung gewünscht wird, ist zudem eine aktuelle Version (höher als 1.0.50) der GNOME-Bibliotheken erforderlich.

Das GUI des GUI-Builders

Ist Glade einmal installiert, startet es mit glade &. Daraufhin erscheinen drei Fenster: das Projekt-, das Paletten- und das Eigenschaftsfenster.

Abbildung 1: Glade auf einen Blick

Abbildung 1: Glade auf einen Blick

Ersteres (links oben in Abbildung 1) bildet die Schaltzentrale, über die sich das aktuelle GUI-Projekt speichern oder ein vorhandenes laden lässt, aber auch die zwei anderen Fenster sowie der ebenfalls in Abbildung 1 gezeigte Widget-Baum zu öffnen sind. Hier können auch Projektoptionen – etwa Projektname, Programmiersprache, Verzeichnis- und Dateinamen – geändert werden.

Das Palettenfenster stellt die Widgets zur Verfügung, aus denen man die Bedieneroberfläche aufbaut. Sie sind in zwei Kategorien unterteilt: GTK+ Basic und GTK+ Additional. Wird Glade mit GNOME-Support installiert, gibt es noch eine dritte namens Gnome.

Die Namen und die aktuellen Eigenschaften eines angeklickten Widgets werden im Eigenschaftsfenster angezeigt. In dessen Karteikarte Signale können Handler-Funktionen an die verschiedenen Ereignissen des aktuellen Widgets gebunden werden.

Der Widget-Baum liefert eine Übersicht über die gesamte Widget-Struktur eines Projekts. Außerdem genügt es, auf eines der in ihm dargestellten Widgets zu klicken, damit dessen Attribute im Eigenschaftsfenster angezeigt werden.

Hello World!

Damit all dies nicht zu theoretisch bleibt, entwickeln wir im folgenden eine sehr einfache “Hallo-Welt”-Applikation (Abbildung 4). Sie besteht aus nur einem Fenster mit einem Knopf, der mit dem originellen Label Hello World! beschriftet ist. Wird der Button angeklickt, gibt die Applikation den Text “Hello World!” in der Konsole aus, aus der sie aufgerufen wurde. Den kompletten Quelltext finden Sie auf der Heft-CD oder unter [3].

Wie jede GUI-Applikation braucht auch diese ein Hauptfenster, in das die übrigen Widgets eingefügt werden: Klickt man auf das Window-Icon in der oberen linken Ecke des Palettenfensters, zeichnet Glade ein neues Fenster, in das man andere Widgets von der Palette hinzufügen kann. Im Eigenschaftsfenster legt man dann den Namen des Fensters, dessen Überschrift, Position usw. fest.

Wichtig ist dabei die Karteikarte Signale (Abbildung 2): Hier wird das Signal delete_event an den Handler on_hello_window_delete_event() gebunden. Dieser sorgt dafür, dass das Programm ordentlich beendet wird, wenn der spätere User das Symbol zum Fensterschließen drückt. Vergessen Sie nach der Angabe des Signalnamens nicht, den Knopf Hinzufügen anzuklicken, damit die Verbindung zwischen Signal und Handler tatsächlich hergestellt wird.

Abbildung 2: Verknüpfen von Ereignissen und Handler-Funktionen

Abbildung 2: Verknüpfen von Ereignissen und Handler-Funktionen

Insbesondere am Anfang kennt man noch nicht alle Signale zu einem bestimmten Widget. Dem hilft der Knopf mit der Beschriftung ... neben dem Feld zur Eingabe von Signalnamen ab. Ein Klick darauf, und schon listet ein Dialog wie in Abbildung 3 rechts die Signale zu den jeweiligen Widgets nach Klassen geordnet auf. Sie lassen sich nun per Mausklick auswählen.

Bislang ist das Hauptfenster unserer Applikation aber noch leer. Das lässt sich schnell ändern: Man klickt einmal auf das Knopf-Symbol im Palettenfenster, anschließend auf die leere Fensterschablone – und voilá: Ein Knopf erblickt das Licht der Welt. Im Eigenschaftsfenster stellt man die Knopf-Eigenschaften beliebig ein. Wichtig ist hier, das Signal clicked anzubinden (Abbildung 3).

Abbildung 3: Wird der Button geklickt, muss ein Handler arbeiten

Abbildung 3: Wird der Button geklickt, muss ein Handler arbeiten

Ein Blick hinter den Kulissen

Alle Signale sind angebunden und alle Widget-Eigenschaften festgelegt – nun gilt es, das Projekt etwa mit Datei / Speichern zu speichern und den Interface-Quelltext zu generieren (z. B. via Datei / Quelltext speichern). Werfen wir jetzt einen Blick auf den von Glade erstellten Code.

Diesen legt das Programm per Default in vier Dateien unterhalb des src-Directories im Projektverzeichnis (im Beispiel hello) ab: main.c ist dafür verantwortlich, dass beim Applikationsstart Fenster auf dem Bildschirm erscheinen. interface.c enthält den von Glade generierten Code für die Bedieneroberfläche – allerdings ohne Funktionalität. callbacks.c beinhaltet die Definitionen aller über das Eigenschaftsfenster angelegten Handler-Funktionen. Hier wird also die eigentliche Programmfunktionalität realisiert. support.c hingegen sammelt Sonderfunktionen zur Vereinfachung der Anbindung von Programminterface und -funktionalität.

Über den Menüeintrag Datei / Projekt Optionen... erhält man einen Dialog zum Ändern dieser Dateinamen. Wie sehr man sie in dessen Karteikarte C Optionen auch manipuliert – ihre Funktionalität wird dabei nicht beeinflusst. Weitere Quelltext-Dateien bzw. Bibliotheken müssen eigenhändig in die Datei Makefile.am im Projektverzeichnis unterhalb von src eingefügt werden. Das ist aber einfach, weil Makefile.am die entsprechenden Einträge bereits parat hält, von denen die wichtigsten Projektname_SOURCES für weitere Quelltext- und Projektname_LDADD für Bibliotheksdateien sind. Die Namen der ersteren fügt man einfach mit Leerzeichen getrennt an; bei der Angabe zusätzlicher Bibliotheken gilt es, sich an die entsprechende gcc-Konvention [4] zu halten (für eine Bibliothek namens libzvt wäre zum Beispiel -lzvt einzufügen).

In hello/src/main.c ist nicht viel zu sehen, eigentlich nur die Funktion main(). Diese verbirgt aber bereits interessante Geheimnisse (Listing 1): Zuallererst wird eine Variable – in Wahrheit ein Objekt – vom Typ GtkWidget * namens window1 deklariert. window1 wird in einem der nächsten Schritte zur Referenz auf das Hauptfenster.

Die anschließend aufgerufene Funktion gtk_set_locale() hat die Aufgabe, das Programm mit lokalen Einstellungen bezüglich Sprache, Datums- bzw. Währungsdarstellung etc. zu versorgen. Da wir uns bei unserer einfachen “Hello World”-Anwendung nicht um die Lokalisierung kümmern, wird daraus aber auch auf “deutschsprachigen” Rechnern nicht auf magische Weise ein “Hallo Welt!”.

Die zweite Funktion, gtk_init(), muss nun vor allen weiteren Methoden aufgerufen werden. Sie sorgt dafür, dass die notwendigen Initialisierungsvorgänge für das Programm stattfinden und bereitet eventuelle Kommandozeilen-Argumente für die weitere Bearbeitung durch GTK+-Funktionen vor. Als nächstes kommt zweimal eine Funktion aus der Datei support.c zum Zuge. Diese hat die Aufgabe, das Programm darüber zu informieren, in welchen Verzeichnissen nach Pixmaps (Icons bzw. Bildern) gesucht werden soll.

Der nun folgende Code-Block ist das Herz der main()-Funktion: Hier bekommt window1 von der in der Datei interface.c enthaltenen Funktion create_hello_window() die Referenz zum Hauptfenster unserer Applikation. gtk_widget_show(window1) ist für das Anzeigen des Fensters zuständig. Als letztes muss man gtk_main() aufrufen, um das Programm in einen Ereignis-Wartezustand zu versetzen. return 0 sorgt dafür, dass main() wie erwartet einen ganzzahligen (Integer-)Wert an die aufrufende Shell zurückgibt.

Listing 1

Die

main()

-Funktion

int
main (int argc, char *argv[])
{
  GtkWidget *window1;
  gtk_set_locale ();
  gtk_init (&argc, &argv);
  add_pixmap_directory (PACKAGE_DATA_DIR "/pixmaps");
  add_pixmap_directory (PACKAGE_SOURCE_DIR "/pixmaps");
/* [Kommentare gelöscht] */
  window1 = create_hello_window ();
  gtk_widget_show (window1);
  gtk_main ();
  return 0;
}

Die Datei interface.c enthält ebenfalls nur eine einzige Funktion: create_hello_window(). Diese ist für den Aufbau des Hauptfensters samt dessen Inhalt zuständig. Als Grundregel gilt: Für jedes neue Fenster einer Applikation, sei es ein “normales” Fenster, ein Dialog oder ein Popup-Menü, wird immer eine neue create-Funktion kreiert. Sie sorgt dafür, dass die anderen Widgets dieses Fensters das Licht der Welt erblicken. Für uns sind nur zwei Funktionsaufrufe wirklich von Bedeutung:

gtk_signal_connect (GTK_OBJECT (hello_window), "delete_event", GTK_SIGNAL_FUNC (on_hello_window_delete_event, NULL);
gtk_signal_connect (GTK_OBJECT (hello_button), "clicked", GTK_SIGNAL_FUNC (on_hello_button_clicked), NULL);

Sie verbinden die Signale delete_event und clicked mit den Callback-Funktionen on_hello_window_delete_event() bzw. on_hello_button_clicked(). Als jeweils erstes Argument ist angegeben, auf welche Objekte wir uns beziehen: hello_window bzw. hello_button.

Doch woher kommen diese beiden Funktionen? Ihre Definitionen enthält die Datei callbacks.c, und hier müssen wir ein wenig programmieren.

Den Inhalt von on_hello_window_delete_event() bildet ein Aufruf der Funktion gtk_exit(0), mit der man das Programm verlässt. printf("Hello World!"); sorgt in on_hello_button_clicked() dafür, dass die Zeichenkette Hello World! in die Konsole geschrieben wird. Listing 2 zeigt den kompletten Inhalt von callbacks.c.

Listing 2

callbacks.c
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif
#include <stdio.h>
#include <gtk/gtk.h>
#include "callbacks.h"
#include "interface.h"
#include "support.h"
/*
 * Diese Funktion sorgt dafür, dass das Programm beim Drücken
 * des Symbols zum Fensterschließen richtig beendet und die
 * Eingabeaufforderung für weitere Kommandos freigegeben wird.
 */
gboolean
on_hello_window_delete_event (GtkWidget *widget,
                              GdkEvent  *event,
                              gpointer  user_data)
{
  /* Aufruf der Funktion zum Verlassen des Programms */
  gtk_exit(0);
  /* FALSE wird zurückgegeben, weil die Funktion die Rückgabe
   * einer booleschen Variable erwartet. Wenn man das nicht
   * macht, meckert der Compiler mit einer Warnung, dass die
   * Funktion nicht richtig geschrieben wurde…
   */
  return FALSE;
}
/*
 * Funktion zur Ausgabe der Zeichenkette "Hello World!" in der
 * Konsole, falls der Knopf mit der Beschriftung "Hello World"
 * gedrückt wird
 */
void
on_hello_button_clicked (GtkButton *button,
                         gpointer  user_data)
{
  printf("Hello World!\n");
}

Letzter Akt

Nun ist das Programm fertig. Um es zu übersetzen, gibt man im Projektverzeichnis folgenden Befehl ein:

./autogen.sh [--prefix=Appverzeichnis]

Damit werden alle für Glade benötigten Systemressourcen überprüft und ein passendes Makefile erzeugt. Mit make starten Sie dann den Kompilierungsvorgang. Aus dem Projektverzeichnis heraus rufen Sie das neuerzeugte Binärprogramm mit relativer Pfadangabe als

./src/hello

auf. make install kopiert das erzeugte Kompilat ins ggf. hinter --prefix= angegebene Applikationsverzeichnis. Bei fehlender Präfixangabe ist dies normalerweise das Verzeichnis /usr/local/bin.

Abbildung 4: Erst ein Klick auf das X-Icon beendet das Programm

Abbildung 4: Erst ein Klick auf das X-Icon beendet das Programm

Wer Appetit auf mehr hat, findet in [5] eine etwas anspruchvollere Beispiel-Applikation, deren Quellcode unter [6] bereitsteht.

Glossar

OO-Entwicklung

Eine Programmierungsmethodik, die daraus besteht, Daten (Zustände) und Funktionen bzw. Methoden (Verhalten) zu Objekten zusammenzukapseln, so wie es bei den Objekten des alltäglichen Lebens üblich ist. (Das Objekt “Zeitschrift” etwa kann den Zustand “gelesen” oder “ungelesen” haben und selbst beispielsweise herunterfallen.) “OO” steht kurz für “Objekt-Orientierung”.

Signal-Handler

Eine Funktion oder ein Programmstück, die bzw. das automatisch beim Auslösen eines Signals (beispielweise durch das Anklicken eines Knopfes) aufgerufen wird. Auch bekannt als Callback-Funktion.

–prefix

Bei der Syntaxangabe (nicht nur in Manpages) werden mögliche, aber nicht notwendige Angaben (hier die Option “–prefix=Wert”) laut Konvention von eckigen Klammern umgeschlossen. Mit dem configure-Präfix beschäftigte sich übrigens der “Answer Girl”-Artikel in Heft 01/2002.

\n

Symbol für einen Zeilenumbruch (“newline”).

Der Autor

Rafael Peregrino da Silva promoviert an der TU Berlin, war bis 09/2001 bei der innominate AG tätig und arbeitet zur Zeit bei der bone labs GmbH.

LinuxUser 02/2002 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben