====== 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 ((Das sind in GTK+ alle Elemente, die eine Benutzeroberfläche ausmachen)) 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 [[gui:gtk:widgets:window|GtkWindow]] implementiert und bildet gewissermaßen die Leinwand, auf der die Oberfläche gezeichnet wird. Nun ist ein [[gui:gtk:widgets:window|GtkWindow]] auch ein [[gui:gtk:widgets:container|GtkContainer]]. Der [[gui:gtk:widgets:container|GtkContainer]] bildet die abstrakte Basisklasse für alle Widgets, die andere Widgets enthalten können. Landet ein Zeichenaufruf nun bei einem [[gui:gtk:widgets:container|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 ===== {{gui:gtk:basics:basics_02_bin.png|GTK+ auf Linux mit Xfce-Design}} Wie bereits angesprochen, ist [[gui:gtk:widgets:container|GtkContainer]] eine abstrakte Klasse. Wie nun der Platz, der dem [[gui:gtk:widgets:container|GtkContainer]] zur Verfügung steht, an die sogenannten //Child//-Widgets verteilt wird, entscheidet die jeweilige GtkContainer-Implementierung. Die einfachste Implementierung hierbei ist [[gui:gtk:widgets:bin|GtkBin]]. Als Widget an sich ist der [[gui:gtk:widgets:bin|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 [[gui:gtk:widgets:bin|GtkBin]] nicht viel einzustellen gibt, reicht das in diesem Fall vollkommen aus. Fassen wir das soeben Gelernte in ein wenig Code zusammen: #include 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 [[gui:gtk:widgets:window|GtkWindow]] ist und [[gui:gtk:widgets:container|GtkContainer]] eine Basisklasse von [[gui:gtk:widgets:window|GtkWindow]] ist. ===== GtkBox: Alles in einer Reihe ===== {{gui:gtk:basics:basics_02_box.png|GTK+ auf Linux mit Xfce-Design}} Wie ihr nun sicher bemerkt habt, kann man beim [[gui:gtk:widgets:bin|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 [[gui:gtk:widgets:box|GtkBox]]. Wie der [[gui:gtk:widgets:container|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 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 ''GtkButton''s 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); ((gboolean und gint sind Typdefinitionen der GLib, einer Bibliothek, auf der GTK basiert)) * ''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 [[gui:gtk:widgets:box|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 ===== {{gui:gtk:basics:basics_02_table.png|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 [[gui:gtk:widgets:table|GtkTable]]. Beginnen wir wieder mit einem Beispiel: #include 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 [[gui:gtk:widgets:table|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 [[gui:gtk:widgets:box|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 [[gui:gtk:widgets|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 ===== {{gui:gtk:basics:basics_02_fixed.png|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 [[gui:gtk:widgets:fixed|GtkFixed]]. #include 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 [[gui:gtk:widgets:fixed|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 [[gui:gtk:widgets:fixed|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 [[gui:gtk:widgets:fixed|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 **[[gui:gtk:basics:signals|Signalen]]**.