GTK mit Builder: Layout

In diesem Kapitel geht es darum, wie man mehrere Widgets gruppieren und in einem Fenster anordnen kann. Layouts werden in der Regel wirksam, wenn die Fenstergröße änderbar ist. In anderen Fällen könnte man die Widgets auch manuell platzieren, wie wir das im „freien Layout“ zeigen. Ändern Sie also beim Ausprobieren ruhig mal die Fenstergröße.

Manuelle Stretchbereiche Bearbeiten

Bei manuellen Stretchbereichen (Panes) geht es darum, einen Bereich in genau zwei Teile zu teilen und die Größe durch den Benutzer festlegen zu lassen. Wir demonstrieren das am Beispiel horizontaler Stretchbereiche, wollen aber bemerken, dass es sich für vertikale analog verhält.

C
#include <gtk/gtk.h>

int main (int argc, char *argv[])
{
    GtkWidget *window, *hpanes, *buttons[2];
    gtk_init (&argc, &argv);
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect (window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
    
    hpanes = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);

    buttons[0] = gtk_button_new_with_label ("erster");
    buttons[1] = gtk_button_new_with_label ("zweiter");

    gtk_paned_add1 (GTK_PANED(hpanes), buttons[0]);
    gtk_paned_add2 (GTK_PANED(hpanes), buttons[1]);
    
    gtk_container_add (GTK_CONTAINER(window), hpanes);
    gtk_widget_show_all (window);
    gtk_main ();
    return 0;
}

In diesem Programm liegen zwei Druckknöpfe nebeneinander. Es wird mit gtk_paned_new() ein Layout erzeugt, welches zwei horizontal (oder ggf. vertikal) nebeneinander liegende Teile beinhaltet. Mit der Funktion gtk_paned_add1() und gtk_paned_add2() werden diesen Bereichen Widgets zugewiesen, in unserem Fall die beiden Knöpfe. Die Größe der Bereiche kann der Benutzer mit einem zwischen den Bereichen liegenden Regler mit der Maus vorgeben. Dem Fenster wird nun dieser Stretchbereich als Widget mit gtk_container_add() hinzugefügt.

Wollen Sie einen Bereich in zwei vertikal getrennte Bereiche teilen, so können Sie der Funktion gtk_paned_new() die Konstante GTK_ORIENTATION_VERTICAL übergeben.

Solche Stretchbereiche lassen sich bequem schachteln, wobei mehr als zwei Schachtelungsebenen schnell unübersichtlich werden. Das folgende Programm zeigt, wie man horizontale und vertikale Stretchbereiche schachtelt.

C
#include <gtk/gtk.h>

int main (int argc, char *argv[])
{
    GtkWidget *window, *hpanes, *vpanes, *buttons[3];
    gtk_init (&argc, &argv);
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect (window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
    
    /* Horizontale Stretchbereiche mit 2 Knöpfen */
    hpanes = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
    buttons[0] = gtk_button_new_with_label ("erster");
    buttons[1] = gtk_button_new_with_label ("zweiter");
    gtk_paned_add1 (GTK_PANED(hpanes), buttons[0]);
    gtk_paned_add2 (GTK_PANED(hpanes), buttons[1]);

    /* Vertikaler Stretchbereich mit einem Knopf und obigem Bereich */
    vpanes = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
    buttons[2] = gtk_button_new_with_label ("dritter");
    gtk_paned_add1 (GTK_PANED(vpanes), buttons[2]);
    gtk_paned_add2 (GTK_PANED(vpanes), hpanes);
    
    gtk_container_add (GTK_CONTAINER(window), vpanes);
    gtk_widget_show_all (window);
    gtk_main ();
    return 0;
}

In diesem Programm wurde ein horizontaler Stretchbereich erzeugt, in dem zwei Knöpfe liegen. Dieser Bereich wird zusammen mit einem weiteren Knopf einem vertikalen Stretchbereich hinzugefügt.

Tabellarisches Layout Bearbeiten

Die tabellarischen Layouts GtkGrid und GtkTable ordnen Widgets tabellarisch an. Einzelne Widgets werden dem Layout hinzugefügt, in dem diese Widgets entlang gedachten Linien an den Rändern der Tabellenzellen eingefügt werden. Hat man eine Tabelle, mit zwei Zeilen und drei Spalten, so kann man beispielsweise ein Widget an die Stelle horizontal 0, 1 und vertikal 0, 1 hinzufügen, um so die Zelle links oben anzusprechen. Möchte man die gesamte letzte Zeile mit einem Widget ausfüllen, reicht horizontal 0, 2 und vertikal 2, 3 aus.

Tabellen können ihre Elemente so anordnen, dass alle Widgets die gleiche Größe bekommen. Hierfür ist ein Attribut namens „homogeneous“ (dt.: „Einheitlichkeit“) zu setzen.

Das folgende Programm zeigt das Tabellenlayout mit je drei Zeilen und Spalten. Mit einem Knopf kann man dafür sorgen, dass die Widgets gleiche Größe bekommen, obwohl sie unterschiedlich viel Platz benötigen.

C
#include <gtk/gtk.h>


static void change_homogenous (GtkWidget *widget, GtkTable *table)
{
    /* Homogenität ändern */
    gtk_table_set_homogeneous (table, !gtk_table_get_homogeneous (table));
}

int main (int argc, char *argv[])
{
    GtkWidget *window, *table, *buttons[5], *labels[4];
    gtk_init (&argc, &argv);
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect (window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    /* Tabelle mit 3 Reihen und 3 Spalten, uneinheitliche Größe */
    table = gtk_table_new (3, 3, FALSE);

    /* Knöpfe und Texte erstellen */
    buttons[0] = gtk_button_new_with_label ("0");
    buttons[1] = gtk_button_new_with_label ("rechts oben");
    buttons[2] = gtk_button_new_with_label ("homogen?");
    g_signal_connect (buttons[2], "clicked",
	G_CALLBACK(change_homogenous), table);
    buttons[3] = gtk_button_new_with_label ("0");
    buttons[4] = gtk_button_new_with_label ("rechts unten");
    labels[0] = gtk_label_new ("N");
    labels[1] = gtk_label_new ("W");
    labels[2] = gtk_label_new ("O");
    labels[3] = gtk_label_new ("S");

    /* erste Reihe */
    gtk_table_attach_defaults (GTK_TABLE(table), buttons[0], 0, 1, 0, 1);
    gtk_table_attach_defaults (GTK_TABLE(table), labels[0], 1, 2, 0, 1);
    gtk_table_attach_defaults (GTK_TABLE(table), buttons[1], 2, 3, 0, 1);

    /* zweite Reihe */
    gtk_table_attach_defaults (GTK_TABLE(table), labels[1], 0, 1, 1, 2);
    gtk_table_attach_defaults (GTK_TABLE(table), buttons[2], 1, 2, 1, 2);
    gtk_table_attach_defaults (GTK_TABLE(table), labels[2], 2, 3, 1, 2);

    /* dritte Reihe */
    gtk_table_attach_defaults (GTK_TABLE(table), buttons[3], 0, 1, 2, 3);
    gtk_table_attach_defaults (GTK_TABLE(table), labels[3], 1, 2, 2, 3);
    gtk_table_attach_defaults (GTK_TABLE(table), buttons[4], 2, 3, 2, 3);

    gtk_container_add (GTK_CONTAINER(window), table);
    gtk_widget_show_all (window);
    gtk_main ();
    return 0;
}

Tabellen werden mit gtk_table_new() erzeugt. Man gibt hier die Anzahl der Zeilen und der Spalten an und, ob die enthaltenen Widgets gleich groß dargestellt werden. Einer Tabelle kann man mit der Funktion gtk_table_attach_defaults() ein Widget hinzufügen. Diesem Widget wird mit den letzten vier Parametern mitgeteilt, wo es sich zu befinden hat, und zwar horizontal und vertikal entlang der Begrenzungslinien.

Die erste Spalte wurde absichtlich mit Widgets gefüllt, die nur sehr wenig Text haben. Dadurch ist ihre Ausdehnung nur sehr gering. Ändert man in der Callback-Funktion change_homogeneous() den Wert für die Einheitlichkeit, werden alle Widgets gleich groß gezeichnet.

Man kann den Boole'schen Wert (also TRUE oder FALSE) für die Einheitlichkeit abfragen mit gtk_table_get_homogeneous() und setzen mit gtk_table_set_homogeneous().

Die Anzahl der Zeilen und Spalten, die eine Tabelle aufnehmen kann, kann man dynamisch zur Laufzeit ändern. Ebenfalls ist es möglich, den Abstand zwischen zwei Widgets durch einen Rand zu vergrößern.

Freies Layout Bearbeiten

Unter einem freien Layout verstehen wir dasjenige Layout, bei dem Sie selbst bestimmen, wohin ihre Widgets positioniert werden. Das hierfür benötigte Layout heißt GtkLayout. Sie erhalten einen eigenen Container mit nahezu beliebiger Größe.

Das folgende Programm zeigt, wie man ein Textfeld und einen Knopf diesem Layout hinzufügt und auf Knopfdruck die Position eines Widgets verändert.

C
#include <gtk/gtk.h>


static void move_button (GtkWidget *button, GtkWidget *layout)
{
    gint x;
    gtk_container_child_get (GTK_CONTAINER(layout), button, "x", &x, NULL);
    x = x % 100 + 10;
    gtk_layout_move (GTK_LAYOUT(layout), button, x, 50);
}

int main (int argc, char *argv[])
{
    GtkWidget *window, *layout, *button, *label;
    gtk_init (&argc, &argv);
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect (window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    layout = gtk_layout_new (NULL, NULL);

    label = gtk_label_new ("Test");
    button = gtk_button_new_with_label ("Drück mich!");
    
    gtk_layout_put (GTK_LAYOUT(layout), label, 10, 10);
    gtk_layout_put (GTK_LAYOUT(layout), button, 20, 100);
    g_signal_connect (button, "clicked", G_CALLBACK(move_button), layout);
    
    gtk_container_add (GTK_CONTAINER(window), layout);
    gtk_widget_show_all (window);
    gtk_main ();
    return 0;
}

Das Layout wird mit gtk_layout_new() bereitgestellt. Optional kann man diesem Container einen horizontalen und einen vertikalen Schieberegler mitgeben. Diese Schieberegler sind sinnvoll, wenn der Bereich sehr groß wird und man nur einen kleinen Bereich darstellen möchte.

Dem freien Layout kann man mit der Funktion gtk_layout_put() ein Widget hinzufügen, wobei man seine Position in Pixelkoordinaten angibt. Die obere linke Ecke ist dabei die Nullposition.

Drückt man auf den Knopf, wird die Funktion move_button() aufgerufen. Mit gtk_container_child_get() ermitteln wir die X-Position unseres Knopfes, modifizieren diese und bewegen mit gtk_layout_move() unseren Knopf an die neue Position.

An diesem Beispiel werden sogleich mehrere Sachen gezeigt. Ein Container kann andere Container enthalten, nicht nur Widgets. Widgets innerhalb von Containern kann man abfragen und modifizieren, in dem man ihre Attribute erfragt oder ändert. In unserem Beispiel wird mit der Funktion gtk_container_child_get() ein Widget innerhalb von GtkLayout abgefragt. Wir wollen etwas über das Attribut „x“ wissen und übergeben der Funktion darum den Attributnamen und einen Zeiger auf einen Integer, um das Ergebnis zu speichern. Da diese Funktion beliebig viele Attribute erfragen kann, wird die Liste der Paare aus Attributnamen und Speicherstelle mit einer NULL terminiert. Zu jedem Widget gibt es eine Vielzahl von Attributen, die sich setzen und abfragen lassen.

Kistenlayout Bearbeiten

Beim Kistenlayout geht es darum, Widgets der Reihe nach horizontal oder vertikal anzuordnen. Hierbei stellt eine GtkVBox alle enthaltenen Widgets vertikal dar, eine GtkHBox horizontal. Verschachteltes Kistenlayout ist dasjenige Layout, welches Sie in Anwendungsprogrammen in der Regel verwenden werden. Kisten sind sehr flexibel und bieten viele Möglichkeiten, um Einfluss zu nehmen.

C
#include <gtk/gtk.h>


int main (int argc, char *argv[])
{
    GtkWidget *window, *vbox, *buttons[4];
    
    gtk_init (&argc, &argv);
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect (window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
    
    vbox = gtk_vbox_new (FALSE, 2);
    
    buttons[0] = gtk_button_new_with_label ("FALSE, FALSE");
    gtk_box_pack_start (GTK_BOX(vbox), buttons[0], FALSE, FALSE, 0);
    buttons[1] = gtk_button_new_with_label ("TRUE, FALSE");
    gtk_box_pack_start (GTK_BOX(vbox), buttons[1], TRUE, FALSE, 0);
    buttons[2] = gtk_button_new_with_label ("TRUE, TRUE");
    gtk_box_pack_start (GTK_BOX(vbox), buttons[2], TRUE, TRUE, 0);
    buttons[3] = gtk_button_new_with_label ("FALSE, TRUE");
    gtk_box_pack_start (GTK_BOX(vbox), buttons[3], FALSE, TRUE, 0);
    
    gtk_container_add (GTK_CONTAINER(window), vbox);
    gtk_widget_show_all (window);
    gtk_main ();
    return 0;
}

Eine vertikale Box wird erzeugt mit gtk_vbox_new(). Der erste Parameter bestimmt, ob allen enthaltenen Widgets der gleiche Platz zur Verfügung gestellt wird (TRUE) oder ob sich jedes Widget soviel vom Platz nimmt, wie es braucht (FALSE). Zwischen zwei eingefügten Widgets kann es zusätzlichen Leerraum geben, eine Art Abstand zwischen den Widgets. Dieser Leerraum wird mit dem zweiten Parameter gesteuert.

Der vertikalen Box werden nun einige Knöpfe von oben nach unten hinzugefügt. Dies geschieht mit der Funktion gtk_box_pack_start(). Mit den letzten drei Parametern steuert man die Art und Weise, wie der von der Box bereitgestellte Raum ausgenutzt wird.

Der dritte Parameter steuert, ob das hinzugefügte Widget ausgedehnt werden soll. Der zur Verfügung stehende Platz wird in gleichen Teilen unter allen Widgets aufgeteilt, die diesen Parameter auf „TRUE“ gesetzt haben.

Hat ein Widget nun einen bestimmten Raum zur Verfügung, kann man mit dem vierten Parameter steuern, ob es diesen Raum ausfüllen soll (TRUE) oder nicht (FALSE). Dieser Parameter wird nicht ausgewertet, wenn der dritte Parameter nicht auf „TRUE“ gesetzt wurde.

Innerhalb des zur Verfügung stehenden Platzes kann es zusätzlichen Leerraum geben, den man mit dem letzten Parameter steuert. In einer vertikalen Box betrifft dies nur den Raum nach oben und unten, nicht jedoch zu dem linken und rechten Rand.

Die Alternative zur vertikalen Box ist die horizontale Box, die mit gtk_hbox_new() erzeugt wird. Hier werden die Elemente von links nach rechts hinzugefügt. Möchte man in einer der Boxen genau anders herum anordnen, bietet sich gtk_box_pack_end() an.

Beim Design einer Box stellt man sich also zuerst die Frage, ob allen enthaltenen Widgets gleich viel Raum bereitgestellt wird. Dann entscheidet man, wie dieser Raum genutzt werden soll.

Das folgende Beispiel zeigt, wie sich horizontale und vertikale Boxen ineinander verschachteln lassen. Ein ähnliches Layout werden wir verwenden, wenn es um Fenster geht, die ein Menü, eine Statuszeile sowie einen Hauptbereich haben.

C
#include <gtk/gtk.h>


int main (int argc, char *argv[])
{
    GtkWidget *window, *hbox, *vbox, *buttons[5];
    
    gtk_init (&argc, &argv);
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect (window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    vbox = gtk_vbox_new (FALSE, 0);
    
    /* Obere Zeile */
    buttons[0] = gtk_button_new_with_label ("Menü");
    gtk_box_pack_start (GTK_BOX(vbox), buttons[0], FALSE, FALSE, 0);

    /* Mittlerer Bereich */
    hbox = gtk_hbox_new (TRUE, 0);
    buttons[1] = gtk_button_new_with_label ("TRUE, TRUE");
    gtk_box_pack_start (GTK_BOX(hbox), buttons[1], TRUE, TRUE, 0);
    buttons[2] = gtk_button_new_with_label ("FALSE, FALSE");
    gtk_box_pack_start (GTK_BOX(hbox), buttons[2], FALSE, FALSE, 0);
    buttons[3] = gtk_button_new_with_label ("TRUE, TRUE");
    gtk_box_pack_start (GTK_BOX(hbox), buttons[3], TRUE, TRUE, 0);
    gtk_box_pack_start (GTK_BOX(vbox), hbox, TRUE, TRUE, 0);

    /* Untere Zeile */
    buttons[4] = gtk_button_new_with_label ("Status");
    gtk_box_pack_start (GTK_BOX(vbox), buttons[4], FALSE, FALSE, 0);
    
    gtk_container_add (GTK_CONTAINER(window), vbox);
    gtk_widget_show_all (window);
    gtk_main ();
    return 0;
}

Dieses Programm zeigt, wie man in eine vertikale Box einen Knopf, dann eine horizontale Box und anschließend wieder einen Knopf packt. Sowohl der obere wie auch der untere Knopf bekommen lediglich die Ausdehnung, die sie benötigen. Der Mittlere Teil hingegen wurde sehr großzügig genutzt.

Zusammenfassung Bearbeiten

In diesem Kapitel haben wir einige Layouts kennen gelernt und gezeigt, wie man Widgets einem Layout hinzufügt. Sie wissen nun, wie man Layouts verschachteln kann, und mit welchen Parametern man das Aussehen beeinflussen kann.