Das Layout

Eine Benutzeroberfläche erfüllt in erster Linie immer eine Funktion - sie ist die Schnittstelle zwischen Mensch und Maschine. Nun müssen jedoch alle Funktionen des Programms auf den Bildschirm, und das so übersichtlich wie möglich.

In dieser Lektion werden wir das „Packen“ von verschiedenen Widgets 1) besprechen.

Das Zeichnen von Widgets

Keine Angst, wir müssen keine eigenen Grafiken erstellen, das übernimmt GTK+ für uns. Doch wie kommt der Inhalt auf den Bildschirm?

GTK+ zeichnet von hinten nach vorne, und ganz hinten befindet sich das Fenster. Dieses ist in GTK+ als GtkWindow implementiert und bildet gewissermaßen die Leinwand, auf der die Oberfläche gezeichnet wird.

Nun ist ein GtkWindow auch ein GtkContainer. Der GtkContainer bildet die abstrakte Basisklasse für alle Widgets, die andere Widgets enthalten können. Landet ein Zeichenaufruf nun bei einem GtkContainer, so berechnet berechnet die jeweilige GtkContainer-Implementierung die Position der gepackten Widgets relativ zur oberen linken Ecke des Fensters. Diese Koordinaten, zusammen mit einer Breiten- und Höhenangabe für den zugewiesenen Bereich, werden an die Zeichenmenthode des jeweiligen Widgets weitergegeben.

GtkBin: Einfach, aber solide

GTK+ auf Linux mit Xfce-Design

Wie bereits angesprochen, ist GtkContainer eine abstrakte Klasse. Wie nun der Platz, der dem GtkContainer zur Verfügung steht, an die sogenannten Child-Widgets verteilt wird, entscheidet die jeweilige GtkContainer-Implementierung. Die einfachste Implementierung hierbei ist GtkBin.

Als Widget an sich ist der GtkBin recht sinnlos: Er erlaubt es uns, ein anderes Widget in sich hineinzupacken. Die Implementierung ist sogar so einfach gehalten, dass wir das Widget mit der GtkContainer-Methode gtk_container_add packen müssen. Diese Methode packt das Widget mit den Standardparametern der jeweiligen Implementierung. Da es beim GtkBin nicht viel einzustellen gibt, reicht das in diesem Fall vollkommen aus.

Fassen wir das soeben Gelernte in ein wenig Code zusammen:

#include <gtk/gtk.h>
 
int main (int argc, char* argv[])
{
    /* Unsere Widgets */
    GtkWidget *window,
              *button;
 
    /* GTK initialisieren */
    gtk_init (&argc, &argv);
 
    /* Das Fenster erstellen */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
 
    /* Den GtkButton erstellen */
    button = gtk_button_new_with_label ("Klick mich!");
    gtk_container_add (GTK_CONTAINER (window), button);
 
    /* Alles zeigen und die Ereignisschleife betreten */
    gtk_widget_show_all (window);
    gtk_main ();
 
    return 0;
}


Richten wir unser Augenmerk auf die Zeile:

gtk_container_add (GTK_CONTAINER (window), button);


An dieser Stelle fügen wir den button in unser Fenster ein. Wie zu erwarten war, bekommen wir von der eigentlichen GtkContainer-Implementierung nicht viel zu sehen. Der GType-Cast GTK_CONTAINER () ist notwendig, weil gtk_container_add einen Pointer vom Typ GtkContainer * erwartet. window ist aber vom Typ GtkWidget *. Hier ist dieser Cast allerdings vollkommen legitim, da unser GtkWidget * ein GtkWindow ist und GtkContainer eine Basisklasse von GtkWindow ist.

GtkBox: Alles in einer Reihe

GTK+ auf Linux mit Xfce-Design

Wie ihr nun sicher bemerkt habt, kann man beim GtkBin nicht wirklich von „Layout“ sprechen. Immerhin verstehen wir darunter eine Anordnung, und diese wird erst interessant, wenn viele verschiedene Dinge angeordnet werden müssen.

Am einfachsten ist hier die GtkBox. Wie der GtkContainer ist dieses Widget nicht direkt implementiert, GtkBox ist als Klasse abstrakt. Es existieren drei Implementierungen:

  • GtkHBox, für die horizontale Anordnung von beliebig vielen unterschiedlichen Widgets
  • GtkVBox, für die vertikale Anordnung von beliebig vielen unterschiedlichen Widgets
  • GtkButtonBox, für die Anordnung von Buttons in einem Dialog, interessiert uns hier nicht.

Ein einfaches Beispiel:

#include <gtk/gtk.h>
 
int main (int argc, char* argv[])
{
    /* Unsere Widgets */
    GtkWidget *window,
              *box,
              *button1,
              *button2,
              *button3;
 
    /* GTK initialisieren */
    gtk_init (&argc, &argv);
 
    /* Das Fenster erstellen */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
 
    /* Die Box erstellen und in das Fenster packen */
    box = gtk_vbox_new (TRUE, 5);
    gtk_container_add (GTK_CONTAINER (window), box);
 
    /* Die GtkButtons erstellen und in die Box packen */
    button1 = gtk_button_new_with_label ("Nummer 1");
    gtk_box_pack_start (GTK_BOX (box), button1, TRUE, TRUE, 0);
    button2 = gtk_button_new_with_label ("Nummer 2");
    gtk_box_pack_start (GTK_BOX (box), button2, TRUE, TRUE, 0);
    button3 = gtk_button_new_with_label ("Nummer 3");
    gtk_box_pack_start (GTK_BOX (box), button3, TRUE, TRUE, 0);
 
    /* Alles zeigen und die Ereignisschleife betreten */
    gtk_widget_show_all (window);
    gtk_main ();
 
    return 0;
}


Kompiliert und ausgeführt, und schon sehen wir drei GtkButtons in einer Reihe untereinander.

Und wieder schauen wir uns zwei Funktionen etwas genauer an:

box = gtk_vbox_new (TRUE, 5);


Hiermit erstellen wir die (vertikale) Box. Analog hierzu würde uns GTK+ die Widgets mit gtk_hbox_new horizontal, also nebeneinander, positionieren. Die Funktionsargumente wären auch in diesem Fall gleich. Die allgemeine Signatur lautet nämlich:

GtkWidget * gtk_[v/h]_box_new (gboolean homogenous, gint spacing);

2)

  • homogenous: TRUE, falls allen Widgets gleich viel Platz zugeteilt werden soll, FALSE wenn nicht
  • spacing: Der Abstand, der zwischen den Widgets eingehalten werden soll.

Kommen wir nun zur zweiten interessanten Funktion:

gtk_box_pack_start (GTK_BOX (box), button1, TRUE, TRUE, 0);


Dies ist eine von zwei möglichen Packfunktionen. Die andere Funktion wäre gtk_box_pack_end, auch mit den gleichen Funktionsargumenten. Der Unterschied liegt in der Art und Weise, wie Elemente hinzugefügt werden. Mit gtk_box_pack_start wird das erste Widget an den Anfang gepackt, alle weiteren danach. Mit gtk_box_pack_end wird das erste Widget an das Ende gepackt, alle weiteren davor, also vom Ende weg.

Sehen wir uns wieder die allgemeine Signatur dieser Funktionen an:

gtk_box_pack_[start/end] (GtkBox *box, GtkWidget *child, gboolean expand, gboolean fill, guint padding);


  • box: Die GtkBox, in die gepackt werden soll.
  • child: Das GtkWidget, welches in die Box gepackt werden soll.
  • expand: Das Verhalten bei Vergrößerung der Box: TRUE, wenn der zusätzliche Platz an das innere Widget weitergegeben werden soll, FALSE wenn nicht.
  • fill: TRUE, wenn das innere Widget bei Platzvergrößerung sich selbst vergrößern soll; FALSE, wenn der zusätzliche Platz „leer“ bleiben soll. Diese Eigenschaft hat keinen Einfluss, wenn expand auf FALSE gesetzt ist. Bei einer vertikalen Box nimmt das Widget immer die ganze Breite ein, bei einer horizontalen Box immer die ganze Höhe.
  • padding: Der Abstand um das Widget herum (zusätzlich zur spacing Eigenschaft der Box, siehe Konstruktor)

GtkTable: Auf in die zweite Dimension

GTK+ auf Linux mit Xfce-Design

Hin und wieder müssen Widgets sowohl nebeneinander, als auch untereinander angeordnet werden. Natürlich könnten wir eine vertikale Box nehmen und in diese horizontale Boxen packen, in die wir wiederum unsere Widgets packen. Da dies jedoch ziemlich kompliziert ist bietet uns GTK+ das GtkTable.

Beginnen wir wieder mit einem Beispiel:

#include <gtk/gtk.h>
 
int main (int argc, char* argv[])
{
    /* Unsere Widgets */
    GtkWidget *window,
              *table,
              *button1,
              *button2,
              *button3;
 
    /* GTK initialisieren */
    gtk_init (&argc, &argv);
 
    /* Das Fenster erstellen */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
 
    /* Das GtkTable erstellen und in das Fenster packen */
    table = gtk_table_new (2, 2, TRUE);
    gtk_container_add (GTK_CONTAINER (window), table);
 
    /* Die GtkButtons erstellen und in das GtkTable packen */
    button1 = gtk_button_new_with_label ("Nummer 1");
	gtk_table_attach_defaults (GTK_TABLE (table), button1, 0, 2, 0, 1);
    button2 = gtk_button_new_with_label ("Nummer 2");
	gtk_table_attach_defaults (GTK_TABLE (table), button2, 0, 1, 1, 2);
    button3 = gtk_button_new_with_label ("Nummer 3");
	gtk_table_attach_defaults (GTK_TABLE (table), button3, 1, 2, 1, 2);
 
    /* Alles zeigen und die Ereignisschleife betreten */
    gtk_widget_show_all (window);
    gtk_main ();
 
    return 0;
}


Im Prinzip funktioniert das GtkTable wie die anderen bisher besprochenen Widgets auch: Zuerst das Layout-Widget erstellen, dann die anderen Widgets in das Layout packen.

table = gtk_table_new (2, 2, TRUE);


Mit gtk_table_new wird das GtkTable erstellt. Sehen wir uns die Signatur dieser Funktion einmal genauer an:

GtkWidget * gtk_table_new (guint rows, guint columns, gboolean homogeneous);


Mit rows (Zeilen) und coloumns (Spalten) können die Ausmaße der Tabelle festgelegt werden. homogenous legt wie bei der GtkBox fest, ob alle Zellen gleich groß sein sollen.

Interessanter ist dagegen die Funktion gtk_table_attach_defaults:

void gtk_table_attach_defaults (GtkTable *table, GtkWidget *widget, 
                                guint left_attach, guint right_attach, 
                                guint top_attach, guint bottom_attach);


  • table: Das GtkTable, in das ein Widget eingefügt werden soll
  • widget: Das einzufügende GtkWidget
  • left_attach: Die Nummer der Spalte, an welcher der linke Rand des Widgets angrenzt (ganz links: 0)
  • right_attach: Die Nummer der Spalte, an welcher der rechte Rand des Widgets angrenzt
  • top_attach: Die Nummer der Zeile, an welcher der obere Rand des Widgets angrenzt (ganz oben: 0)
  • bottom_attach: Die Nummer der Zeile, an welcher der untere Rand des Widgets angrenzt

Wie im Beispiel zu sehen ist, kann sich ein Widget auch über mehrere Zellen ausbreiten.

GtkFixed: Volle Kontrolle, mit hohem Preis

GTK+ auf Linux mit Xfce-Design

In einigen seltenen Fällen muss ein Widget an einer ganz bestimmten Position zu finden sein. Für diese seltenen Fälle gibt es GtkFixed.

#include <gtk/gtk.h>
 
int main (int argc, char* argv[])
{
    /* Unsere Widgets */
    GtkWidget *window,
              *fixed,
              *button;
 
    /* GTK initialisieren */
    gtk_init (&argc, &argv);
 
    /* Das Fenster erstellen */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
 
    /* Das GtkFixed erstellen und in das Fenster packen */
    fixed = gtk_fixed_new ();
    gtk_container_add (GTK_CONTAINER (window), fixed);
 
    /* Den GtkButton erstellen und in das GtkFixed legen */
    button = gtk_button_new_with_label ("Klick mich!");
    gtk_fixed_put (GTK_FIXED (fixed), button, 64, 32);
 
    /* Alles zeigen und die Ereignisschleife betreten */
    gtk_widget_show_all (window);
    gtk_main ();
 
    return 0;
}


Auch das GtkFixed besitzt einen (recht einfachen) Konstruktor und eine „Einfüge“-Funktion. Betrachten wir letztere:

void gtk_fixed_put (GtkFixed *fixed, GtkWidget *widget, gint x, gint y);


x und y bezeichnen hier die Koordinaten der linken oberen Ecke des Widgets in Pixeln. Der Koordinatenursprung ist in der linken oberen Ecke des GtkFixed.

Hinweis: Benutzt dieses Widget nur mit Bedacht! Das GtkFixed nimmt keine Größenanpassungen der Widgets vor, die Positionen sind absolut! Stellt euch vor, jemand möchte eure Anwendung übersetzen, und der Text im GtkButton wird größer. Jegliche Widgets, die neben dem GtkButton positioniert sind, werden dann von ihm überlappt. Das sieht nicht nur hässlich aus, sondern macht eure Oberfläche praktisch unbenutzbar.

Also: Nur wenn es sich wirklich nicht vermeiden lässt auf GtkFixed zurückgreifen!

Fazit

Wie ihr seht, bietet GTK+ Layout-Container für alle denkbaren Einsatzfälle. Zwar konnte diese Lektion nur einen kleinen Überblick über die verschiedenen Layout-Container liefern, doch prägt euch zumindest ihre Namen und ihre Funktion ein: Sie werden uns das ganze Tutorial hindurch begleiten!

Weiter geht es in der nächsten Lektion mit dem „aktiven“ Teil einer Benutzeroberfläche, den Signalen.

1)
Das sind in GTK+ alle Elemente, die eine Benutzeroberfläche ausmachen
2)
gboolean und gint sind Typdefinitionen der GLib, einer Bibliothek, auf der GTK basiert