[X] GTKmm Tutorial Teil2
-
Grafische Benutzerschnittstellen in C++ mit GTKmm Betriebssystemunabhängig gestalten Teil 2
Nach einer langen Auszeit habe ich mich dazu aufgerafft endlich am Tutorial weiter zu schreiben. Ich hoffe ihr könnt mir diese Pause verzeihen
Wie versprochen behandele ich in diesem Teil das GTK+ Boxensystem, Labels, Buttons und Signale (Events). Ich werde ein paar Anwendungsbeispiele zu dem jeweiligen Thema aufzeigen. Zum Schluß werde ich ein Beispiel mit allen Widgets zeigen und die eventuell noch unbekannten Schritte zusätzlich kommentieren.
1.GTK+ Boxensystem
1.1 Einführung
In GTK+ und somit auch in GTKmm werden die Widgets mit sogenannten „boxes“ angeordnet. Das sind Container die, die Widgets aufnehmen können. Mit diesem Konzept ordnet man Widgets nur relativ und nicht absolut an, im Gegensatz zu dem wie man es von den bekannteren GUI-Designern kennt (z.b. Borland C++ Builder, Visual C++, Visual C# u.s.w.)
Dieses Konzept ermöglicht ein dynamisches Anpassen der Größe der Widgets zur jeweiligen Fenstergröße ohne das sich der Programmierer darum kümmern müsste.
Von diesen Containern gibt es verschiedene Arten.
Es gibt Container die nur jeweils ein Widget aufnehmen können ( diese wurden von Gtk::Bin abgeleitet ), Container die mehrere Widgets aufnehmen können aber nur in eine Richtung, sprich Horizontal oder Vertikal. Dies wären z.B. Gtk::Hbox für die horizontale und Gtk::VBox für die vertikale Richtung.
Und zu guter letzt gibt es noch eine Tabelle ( Gtk::Table ) darin kann man n x m Widgets unterbringen je nach dem wie man es braucht, eben in einer tabellarischen Anordnung.Eine Besonderheit ist aber das Widget Gtk::Fixed. Dies kann Elemente an festen Koordinaten aufnehmen welche dann aber nicht resizable sind.
Ich werde auf diese Widgets nicht näher eingehen, da ich der Meinung bin das man auf diese auch Verzichten kann und mir das Programmieren mit dem Boxensystem wesentlich einfacher und eleganter erscheint.1.2 Widgets und Boxes
Die Container Gtk::HBox und Gtk::VBox haben neben dem Standard Konstruktor einen Konstruktor mit folgenden Parametern:
Gtk::HBox( bool homogenous , int padding ); Gtk::VBox( bool homogenous , int padding );
homogenous gibt hier an das der Platz der Widgets gleichmässig aufgeteilt wird wenn man true übergibt. Der Parameter padding gibt an wieviel Platz in Pixeln zwischen den Bereichen der Widgets sein soll.
Um Widgets in die Boxes einzufügen haben diese folgende Methoden
void pack_start(Gtk::Widget& child, PackOptions options = PACK_EXPAND_WIDGET, guint padding = 0); void pack_end(Gtk::Widget& child, PackOptions options = PACK_EXPAND_WIDGET, guint padding = 0);
Der erste Parameter ist eine Referenz auf das Widget das wir hinzufügen wollen. Z.b. ein Gtk::Button Objekt.
Der zweite Parameter gibt die PackOption an. Davon hängt es ab wie sich die Anordnung der Widgets abspielt.
Es gibt 3 Verschiedene Optionen:
* PACK_SHRINK
* PACK_EXPAND_WIDGET
* PACK_EXPAND_PADDINGGibt man PACK_SHRINK an, wird nur soviel Platz verwendet wie das Widget wirklich braucht und es wird niemals expandiert.
Bei PACK_EXPAND_WIDGET wird der freie Platz ausgefüllt in dem das Widget einfach vergrößert wird.
Bei PACK_EXPAND_PADDING wird der freie Platz einfach durch abstände ausgefüllt, das Widget ist dann sogesehen in zentriert in seinem Bereich. Der Bereich ist abhängig von dem drumherum und das wird alles immer dynamisch angepasst.
Der dritte Parameter padding gibt an wieviel Platz das Widget drum herum haben soll. Im gegensatz zu dem Parameter des Konstruktors ist hier der Platz um das Widget herum gemeint.
1.3 Widgets und Tables
Gtk::Table hat folgenden Konstruktor:
Gtk::Table::Table(guint n_rows = 1, guint n_columns = 1, bool homogeneous = false)
Im Grunde ist dieser eigentlich selbsterklärend. n_rows gibt an wieviel Zeilen das Table haben soll und n_columns wieviel Zeilen.
homogenous gibt an ob die Zellen homogen sprich immer gleich groß sein sollen.
Generell wäre ich vorsichtig mit der Verwendung von homogenous = true da es viele Widgets gibt die sehr viel Platz brauchen und das damit eine negative Auswirkung auf alle Widgets haben kann. Aber natürlich kann dieser Effekt auch erwünscht sein.
Das beste ist das man sich generell erst mal mit den ganzen Containern auseinander setzt und damit etwas rumspielt bis man das ganze verstanden hat wie das läuft.Das ist einfacher als ellenlange Texte darüber zulesen. Die eigene Erfahrung ist immer noch die lehrreichste. (Das ist meine Meinung )
So und nun möchte ich euch erstmal schocken:
void Gtk::Table::attach(Gtk::Widget& child, guint left_attach, guint right_attach, guint top_attach, guint bottom_attach, guint xoptions = Gtk::FILL | Gtk::EXPAND, guint yoptions = Gtk::FILL | Gtk::EXPAND, guint xpadding = 0, guint ypadding = 0);
Diese Methode ist natürlich auf den ersten Blick ziemlich abschreckend aber die Verwendung ist einfacher als es auf den ersten Blick aussehen mag.
child ist das Widget das wir einfügen möchten und als Referenz auf das Widget-Objekt übergeben
left_attach gibt an welche Zelle die Startposition auf der X Achse ist
right_attach gibt an welche Zelle die Endposition auf der X Achse ist wobei die Endposition mindestens 1 größer als die Startposition sein muss
top_attach gibt an welche Zelle die Startposition auf der Y Achse ist
bottom_attach gibt an welche Zelle die Endposition auf der Y Achse ist wobei die Endposition mindestens 1 größer als die Startposition sein mussxoptions gibt an wie sich das Widget innerhalb seiner Zelle auf der X-Achse verhalten soll. Dies ist ähnlich den PackOptions bei den Boxes
yoptions gibt an wie sich das Widget innerhalb seiner Zelle auf der Y-Achse verhalten soll. Dies ist ähnlich den PackOptions bei den Boxesxpadding gibt an wieviel Platz das Widget Links und Rechts von sich haben soll (freier Platz)
xpadding gibt an wieviel Platz das Widget Oben und Unten von sich haben soll (freier Platz)Um das ganze zuverstehen schauen wir uns einfach mal den Aufbau einer Tabelle an:
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | d | e | f | +---+---+---+ 2 | g | h | i | +---+---+---+
Ich habe die Buchstaben in die Tabelle eingefügt um besser erklären zukönnen wie das ganze dann beim Aufruf auszusehen hat, die Zahlen entsprechend dem Zeilen- und Reihenindex
Angenommen wir wollen hier Widget 'a' in Zelle 0/0 einfügen würde der Aufruf folgendermaßen aussehen:
m_table.attach( a , 0 , 1 , 0 , 1 );
für b sieht das ganze so aus:
m_table.attach( b , 1 , 2 , 0 , 1 );
für e sieht das ganze dann so aus:
m_table.attach( e , 1 , 2 , 1 , 2 );
für i sieht das ganze dann so aus:
m_table.attach( e , 2 , 3 , 2 , 3 );
Nun möchte man aber das ein Widget sich über mehrere Zellen erstreckt. Wie das Widget 'd' in diesem Beispiel:
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | d | +---+---+---+ 2 | e | f | g | +---+---+---+
Dann würde der das Einfügen für das Widget 'd' folgendermaßen aussehen:
m_table.attach( d , 0 , 3 , 1 , 2 );
Und für folgendes Layout
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | | + d + 2 | | +---+---+---+
so:
m_table.attach( d , 0 , 3 , 1 , 3 );
Bei den xoptions und yoptions gibt es folgende Optionen:
* FILL
* SHRINK
* EXPANDBei Gtk::FILL wird das Widget, sollte es kleiner als die Zelle selbst sein auf Zellengröße vergrößert.
Bei Gtk::EXPAND zwingt das Table zum größer werden sollte der Platz für das Widget nicht ausreichen.
Wenn die Tabelle weniger Platz bekommt als das sie braucht, weil der Benutzer z.b. die Fenstergröße ändert verschwindet das Wigdet einfach. Wenn man Gtk::SHRINK angibt werden die Widgets in der Tabelle verkleinert damit sie in den Bereich passen.1.4 Single Widget Container
Es gibt auch Container-Widgets die nur ein einziges Widget aufnehmen können. Bestes Beispiel ist Gtk::Window.
Diese Widgets haben nur die Methode add zum hinzufügen der Widgets und nehmen als Parameter eine Referenz auf das Widget das hinzugefügt werden soll.
Es gibt auch noch Ausnahmen bei den "Single Widget Containern" das ist Gtk::Paned aber auf das werde ich ein andere mal zusprechen kommen
1.5 Anwendung
In dem Beispiel werde ich bereits das erste mal Gtk::Label benutzen um zu verdeutlichen wie man die Boxes verwendet.
struct MyWindow : Gtk::Window { MyWindow(); Gtk::VBox m_vbox; Gtk::HBox m_hbox; Gtk::Label m_label1; Gtk::Label m_label2; Gtk::Label m_label3; }; MyWindow::MyWindow() : Gtk::Window(), m_vbox(true,5),// Homogene Ausrichtung und 5 px Abstand m_hbox(true,5),// Homogene Ausrichtung und 5 px Abstand m_label1("Label 1"), m_label2("Label 2"), m_label3("Label 3") { // Text der Titelleiste setzen set_title("GTKmm Tutorial Teil 2"); // m_label1 der horizontalen box als erstes element übergeben m_hbox.pack_start(m_label1); // m_label1 der horizontalen box als zweites element übergeben m_hbox.pack_start(m_label2); // m_hbox der vertikalen box als erstes element übergeben m_vbox.pack_start(m_hbox); // m_hbox der vertikalen box als zweites element übergeben m_vbox.pack_start(m_label3); // Die vertikale Box an das Fenster übergeben add(m_vbox); // sorgt dafür das alle widgets angezeigt werden show_all_children(); }
Und so siehts aus:
Ein Beispiel für die Verwendung eines Gtk::Table Containers findet ihr im Anwendungsbeispiel für die Buttons
2.Signale
2.1 Einführung
In GTKmm wird, um auf Signale/Events zu reagieren, die Bibliothek SigC++ verwendet. SigC++ ist sehr flexibel um mächtig und kann ist sehr vielseitig verwendbar. Es ist möglich Funktionen und Methoden aller art zu Verwenden und es besteht sogar die Möglichkeit zusätzliche Parameter an ein Signal zu binden um zusätzliche Daten an den eigenen Signalhandler zu übergeben.
Um nicht zu ausschweifend zu werden zeige ich euch einfach ein paar Verwendungsbeispiele:
2.2 Anwendung
2.2.1 Eine Funktion als Signal Handler
void on_button_clicked(); // Unser Signal handler Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur veranschaulichung. button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
Wie man sieht ist die Verwendung recht unkompliziert. Die meisten Widgets haben spezielle Zugriffs Methoden die es einem erlauben Signalhandler zu setzen.
Gtk::Button hat in diesem fall z.b. die Methode Gtk::Button::signal_clicked() um den Handler zusetzen der auf ein Klick-Ereignis reagiert.
Die Signale aller Widgets zu beschreiben würde den Rahmen dieses Tutorials sprengen und sollten daher der GTKmm Dokumentation entnommen werden.
2.2.2 Methoden als Signalhandler
struct test { void on_button_clicked(); // Unser Signalhandler }; Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur veranschaulichung. test test_obj; button.signal_clicked().connect(sigc::mem_fun(test_obj,&test::on_button_clicked));
Um Methoden zu binden bietet SigC++ die Funktion sigc::mem_fun
Der als ersten Parameter nimmt sigc::mem_fun eine Referenz auf das Objekt der Methode an und als 2. Parameter den Methodenzeiger auf die Methode.
2.2.3 Binden von Parametern
Da man manchmal zusätzliche Parameter bei einem Ereignis braucht um auf ein Signal zu reagieren bietet SigC++ die Funktion sigc::bind an um zusätzliche Parameter an den Signalhandler zu übergeben.
struct data {}; struct test { void on_button_clicked(data d); }; Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur veranschaulichung. test test_obj; data d; button.signal_clicked().connect( sigc::bind<data>( sigc::mem_fun( test_obj , &test::on_button_clicked ), d ) );
3.Buttons
3.1 Einführung
In GTKmm gibt es neben den "normalen" Buttons auch RadioButtons, CheckButtons (aka CheckBox) und so genannte ToggleButtons. Viel zu erzählen gibt es hier nicht.
Schaut euch einfach mal das Beispiel an und ihr seht das die Verwendung sehr simpel ist. In diesem Beispiel kann man auch schön die Verwendung der oben besprochenen Parameterbindung sehen. Desweiteren vewende ich ein Gtk::Table als Container um dessen Verwendung auch darzustellen.
3.2 Anwendung
struct MyWindow : Gtk::Window { MyWindow(); private: Gtk::ToggleButton m_toggle_button; Gtk::CheckButton m_check_button1; Gtk::CheckButton m_check_button2; Gtk::RadioButtonGroup m_radiogroup; Gtk::RadioButton m_radio_button1; Gtk::RadioButton m_radio_button2; Gtk::Button m_button; Gtk::Table m_table; private: void attach_widgets_to_table(); void connect_signals(); void on_toggle_button_clicked(); void on_radio_button_clicked(int); void on_check_button_clicked(int,Gtk::CheckButton const *); void on_button_clicked(); }; MyWindow::MyWindow() : Gtk::Window(), m_toggle_button("Gtk::ToggleButton"), m_check_button1("Gtk::CheckButton 1"), m_check_button2("Gtk::CheckButton 2"), m_radiogroup(), m_radio_button1(m_radiogroup,"Gtk::RadioButton 1"),// RadioButton einer Gruppe zu ordnen und Beschriftung geben m_radio_button2(m_radiogroup,"Gtk::RadioButton 2"),// RadioButton einer Gruppe zu ordnen und Beschriftung geben m_button("Gtk::Button"), m_table(3,2,true) // 3 Zeilen , 2 Spalten , Homogene aufteilung der Zellen { // Standard Fenstergröße setzen Breite: 400px Höhe: 170px set_default_size(400,170); // Titel setzen set_title("GTKmm Tutorial Teil 2"); // Widgets in das Table einfügen attach_widgets_to_table(); // Signale verbinden connect_signals(); // Tabelle dem Fenster übergeben add(m_table); // Alle Widgets anzeigen show_all_children(); } void MyWindow::attach_widgets_to_table() { // Button in Zelle(0,0) einfügen m_table.attach(m_button,0,1,0,1); // ToggleButton in Zelle(1,0) einfügen m_table.attach(m_toggle_button,1,2,0,1); // CheckButton in Zelle(0,1) einfügen m_table.attach(m_check_button1,0,1,1,2); // CheckButton in Zelle(1,1) einfügen m_table.attach(m_check_button2,1,2,1,2); // RadioButton in Zelle(0,2) einfügen m_table.attach(m_radio_button1,0,1,2,3); // RadioButton in Zelle(1,2) einfügen m_table.attach(m_radio_button2,1,2,2,3); } void MyWindow::connect_signals() { // Signale verbinden: m_button.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button_clicked)); m_toggle_button.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_toggle_button_clicked)); m_radio_button1.signal_clicked().connect(sigc::bind<int>(sigc::mem_fun(*this,&MyWindow::on_radio_button_clicked),1)); m_radio_button2.signal_clicked().connect(sigc::bind<int>(sigc::mem_fun(*this,&MyWindow::on_radio_button_clicked),2)); m_check_button1.signal_clicked().connect(sigc::bind<int,Gtk::CheckButton const *>(sigc::mem_fun(*this,&MyWindow::on_check_button_clicked),1,&m_check_button1)); m_check_button2.signal_clicked().connect(sigc::bind<int,Gtk::CheckButton const *>(sigc::mem_fun(*this,&MyWindow::on_check_button_clicked),2,&m_check_button2)); } void MyWindow::on_toggle_button_clicked() { Glib::ustring msg = "ToggleButton wurde angeklickt. Neuer Status ist: "; if(m_toggle_button.get_active()) msg += "gedrueckt"; else msg += "nicht gedrueckt"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_radio_button_clicked(int which) { Glib::ustring msg = "Der Status von RadioButton "; if(which == 1) msg += "1 hat sich geaendert"; else msg += "2 hat sich geaendert"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_check_button_clicked(int which, Gtk::CheckButton const * cb) { if(!cb) { Gtk::MessageDialog dia(*this,"Es ist ein Fehler aufgetreten",false,Gtk::MESSAGE_ERROR); dia.run(); return; } Glib::ustring msg = "CheckButton "; if(which == 1) msg += "1"; else msg += "2"; if(cb->get_active()) msg += " markiert"; else msg += " Markierung aufgehoben"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_button_clicked() { Gtk::MessageDialog dia(*this,"Button wurde angeklickt :)"); dia.run(); } int main(int argc, char **argv) { Gtk::Main main(argc,argv); MyWindow window; main.run(window); return 0; }
Und so siehts aus:
4.Implementation eines weiteren Beispiels
#include <gtkmm.h> struct MyWindow : Gtk::Window { MyWindow(); ~MyWindow(); void on_button1_clicked(); void on_button2_clicked(); Gtk::Button m_button1; Gtk::Button m_button2; Gtk::Label m_label; Gtk::VBox m_vbox; Gtk::HBox m_hbox; }; MyWindow::MyWindow() : Gtk::Window(), m_button1("Klick mich1"), m_button2("Klick mich2"), m_label("<u><i><b>Ich bin ein Label</b></i></u>"), m_vbox(true,5), m_hbox(true,5) { set_title("GTKmm Tutorial Teil 2"); m_label.set_use_markup(true); m_hbox.pack_start(m_button1); m_hbox.pack_end(m_button2); m_button1.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button1_clicked)); m_button2.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button2_clicked)); m_vbox.pack_start(m_hbox); m_vbox.pack_start(m_label); add(m_vbox); set_default_size(200,100); show_all_children(true); } MyWindow::~MyWindow() { } void MyWindow::on_button1_clicked() { m_label.set_markup("<b><i>Button 1</i> wurde angeklickt</b>"); m_label.set_use_markup(true); } void MyWindow::on_button2_clicked() { m_label.set_markup("<b><u>Button 2</u> wurde angeklickt</b>"); m_label.set_use_markup(true); } int main(int argc, char **argv) { Gtk::Main main(argc,argv); MyWindow window; main.run(window); return 0; }
Und last but not least: So siehts aus:
So diesmal gabs viel code und wenig Erklärungen so wird es auch im nächsten Teil aussehen da ich nicht glaube das es zu den Widgets viel zu erzählen gibt. Ich hoffe das dieses Tutorial hilfreich war.
Ich schmeiße mich gleich auch ans nächste Tutorial damit da nicht wieder so ne Ewigkeit dazwischen liegt
BR
evilissimo
-
So der Teil kann ins technische Review. Wenn zusätzliche Fragen aufkommen bitte stellen damit ich weiß was evtl noch erklärt werden muss.
Ich mach auch gleich am nächsten weiter damit da auch wieder was kommt.
BR
-
evilissimo schrieb:
Ich mach auch gleich am nächsten weiter damit da auch wieder was kommt.
-
Ich habe mir Gedanken über diesen Artikel gemacht und bin zu dem Schluss gekommen das ich zuwenig zu den Container-Widgets erzählt hab und habe das Thema noch etwas ausführlicher behandelt.
@GPC da du ja imho auch ein sehr gutes GTKmm Knowhow hast möchte ich dich darum bitten den Artikel zu lesen und mir zusagen ob da auch alles richtig ist oder ob ich da irgendwas falsch erklärt habe.
Danke
BR
-
evilissimo schrieb:
@GPC da du ja imho auch ein sehr gutes GTKmm Knowhow hast möchte ich dich darum bitten den Artikel zu lesen und mir zusagen ob da auch alles richtig ist oder ob ich da irgendwas falsch erklärt habe.
Der Artikel ist technisch einwandfrei. Sobald du die Änderungen durchgeführt hast, werd ich das auch noch lesen, dann kann der in die Rechtschreibkorrektur.
-
GPC schrieb:
evilissimo schrieb:
@GPC da du ja imho auch ein sehr gutes GTKmm Knowhow hast möchte ich dich darum bitten den Artikel zu lesen und mir zusagen ob da auch alles richtig ist oder ob ich da irgendwas falsch erklärt habe.
Der Artikel ist technisch einwandfrei. Sobald du die Änderungen durchgeführt hast, werd ich das auch noch lesen, dann kann der in die Rechtschreibkorrektur.
Das hab ich schon längst gemacht *fg* Wenn du das grad gelesen hast dann hast du den neuen Teil schon mitgelesen
Ich bin vorher nicht so auf die Parameter für die Boxes und das Table eingegangen. Das ist aber nu schon drin. Habs auf [R] gesetzt
BR
Vinzenz
-
evilissimo schrieb:
GPC schrieb:
evilissimo schrieb:
@GPC da du ja imho auch ein sehr gutes GTKmm Knowhow hast möchte ich dich darum bitten den Artikel zu lesen und mir zusagen ob da auch alles richtig ist oder ob ich da irgendwas falsch erklärt habe.
Der Artikel ist technisch einwandfrei. Sobald du die Änderungen durchgeführt hast, werd ich das auch noch lesen, dann kann der in die Rechtschreibkorrektur.
Das hab ich schon längst gemacht *fg* Wenn du das grad gelesen hast dann hast du den neuen Teil schon mitgelesen
okay, es ist eindeutig, ich bin zu langsam für dich
Ich hab das neue schon mitgelesen, also alles in bester Ordnung.
-
Grafische Benutzerschnittstellen in C++ mit GTKmm Betriebssystemunabhängig gestalten Teil 2
Nach einer langen Auszeit habe ich mich dazu aufgerafft, endlich am Tutorial weiter zu schreiben. Ich hoffe, ihr könnt mir diese Pause verzeihen
Wie versprochen behandele ich in diesem Teil das GTK+ Boxensystem, Labels, Buttons und Signale (Events). Ich werde ein paar Anwendungsbeispiele zu dem jeweiligen Thema aufzeigen. Zum Schluss werde ich ein Beispiel mit allen Widgets zeigen und die eventuell noch unbekannten Schritte zusätzlich kommentieren.
1.GTK+ Boxensystem
1.1 Einführung
In GTK+ und somit auch in GTKmm werden die Widgets mit so genannten „boxes“ angeordnet. Das sind Container, die die Widgets aufnehmen können. Mit diesem Konzept ordnet man Widgets nur relativ und nicht absolut an, im Gegensatz zu dem wie man es von den bekannteren GUI-Designern kennt (z.b. Borland C++ Builder, Visual C++, Visual C# usw.)
Dieses Konzept ermöglicht ein dynamisches Anpassen der Größe der Widgets zur jeweiligen Fenstergröße, ohne das sich der Programmierer darum kümmern müsste.
Von diesen Containern gibt es verschiedene Arten.
Es gibt Container die nur jeweils ein Widget aufnehmen können ( diese wurden von Gtk::Bin abgeleitet ), Container die mehrere Widgets aufnehmen können, aber nur in eine Richtung, sprich Horizontal oder Vertikal. Dies wären z.B. Gtk::Hbox für die horizontale und Gtk::VBox für die vertikale Richtung.
Und zu guter Letzt gibt es noch eine Tabelle ( Gtk::Table ) darin kann man n x m Widgets unterbringen je nach dem wie man es braucht, eben in einer tabellarischen Anordnung.Eine Besonderheit ist aber das Widget Gtk::Fixed. Dies kann Elemente an festen Koordinaten aufnehmen, welche dann aber nicht resizable sind.
Ich werde auf diese Widgets nicht näher eingehen, da ich der Meinung bin, dass man auf diese auch verzichten kann und mir das Programmieren mit dem Boxensystem wesentlich einfacher und eleganter erscheint.1.2 Widgets und Boxes
Die Container Gtk::HBox und Gtk::VBox haben neben dem Standard-Konstruktor einen Konstruktor mit folgenden Parametern:
Gtk::HBox( bool homogenous , int padding ); Gtk::VBox( bool homogenous , int padding );
homogenous gibt hier an, dass der Platz der Widgets gleichmäßig aufgeteilt wird wenn man true übergibt. Der Parameter padding gibt an wie viel Platz in Pixeln zwischen den Bereichen der Widgets sein soll.
Um Widgets in die Boxes einzufügen, haben diese folgende Methoden:
void pack_start(Gtk::Widget& child, PackOptions options = PACK_EXPAND_WIDGET, guint padding = 0); void pack_end(Gtk::Widget& child, PackOptions options = PACK_EXPAND_WIDGET, guint padding = 0);
Der erste Parameter ist eine Referenz auf das Widget, das wir hinzufügen wollen, z.B. ein Gtk::Button Objekt.
Der zweite Parameter gibt die PackOption an. Davon hängt es ab, wie sich die Anordnung der Widgets abspielt.
Es gibt 3 verschiedene Optionen:
- PACK_SHRINK
- PACK_EXPAND_WIDGET
- PACK_EXPAND_PADDING
Gibt man PACK_SHRINK an, wird nur so viel Platz verwendet, wie das Widget wirklich braucht und es wird niemals expandiert.
Bei PACK_EXPAND_WIDGET wird der freie Platz ausgefüllt in dem das Widget einfach vergrößert wird.
Bei PACK_EXPAND_PADDING wird der freie Platz einfach durch abstände ausgefüllt, das Widget ist dann so gesehen zentriert in seinem Bereich. Der Bereich ist abhängig von dem Drumherum und das wird alles immer dynamisch angepasst.
Der dritte Parameter padding gibt an wie viel Platz das Widget drum herum haben soll. Im Gegensatz zu dem Parameter des Konstruktors ist hier der Platz um das Widget herum gemeint.
1.3 Widgets und Tables
Gtk::Table hat folgenden Konstruktor:
Gtk::Table::Table(guint n_rows = 1, guint n_columns = 1, bool homogeneous = false)
Im Grunde ist dieser eigentlich selbsterklärend. n_rows gibt an, wie viele Zeilen das Table haben soll und n_columns wie viele Spalten.
homogenous gibt an ob die Zellen homogen, sprich immer gleich groß sein sollen.
Generell wäre ich vorsichtig mit der Verwendung von homogenous = true, da es viele Widgets gibt, die sehr viel Platz brauchen und es damit eine negative Auswirkung auf alle Widgets haben kann. Aber natürlich kann dieser Effekt auch erwünscht sein.
Das Beste ist, dass man sich generell erst mal mit den ganzen Containern auseinander setzt und damit etwas rumspielt, bis man das alles verstanden hat.~Ich habe diesen Satz ein wenig verändert, da er sich etwas merkwürdig anhörte Ich hoffe, das ist in Ordnung~
Das ist einfacher, als ellenlange Texte darüber zu lesen. Die eigene Erfahrung ist immer noch die lehrreichste. (Das ist meine Meinung )So und nun möchte ich euch erstmal schocken:
void Gtk::Table::attach(Gtk::Widget& child, guint left_attach, guint right_attach, guint top_attach, guint bottom_attach, guint xoptions = Gtk::FILL | Gtk::EXPAND, guint yoptions = Gtk::FILL | Gtk::EXPAND, guint xpadding = 0, guint ypadding = 0);
Diese Methode ist natürlich auf den ersten Blick ziemlich abschreckend, aber die Verwendung ist einfacher, als es auf den ersten Blick aussehen mag.
child ist das Widget, das wir einfügen möchten und als Referenz auf das Widget-Objekt übergeben wird
left_attach gibt an, welche Zelle die Startposition auf der X-Achse ist
right_attach gibt an, welche Zelle die Endposition auf der X-Achse ist, wobei die Endposition mindestens 1 größer als die Startposition sein muss
top_attach gibt an, welche Zelle die Startposition auf der Y-Achse ist
bottom_attach gibt an, welche Zelle die Endposition auf der Y-Achse ist, wobei die Endposition mindestens 1 größer als die Startposition sein mussxoptions gibt an, wie sich das Widget innerhalb seiner Zelle auf der X-Achse verhalten soll. Dies ist ähnlich den PackOptions bei den Boxes
yoptions gibt an, wie sich das Widget innerhalb seiner Zelle auf der Y-Achse verhalten soll. Dies ist ähnlich den PackOptions bei den Boxesxpadding gibt an, wie viel Platz das Widget links und rechts von sich haben soll (freier Platz)
xpadding gibt an, wie viel Platz das Widget oben und unten von sich haben soll (freier Platz)Um das Ganze zu verstehen, schauen wir uns einfach mal den Aufbau einer Tabelle an:
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | d | e | f | +---+---+---+ 2 | g | h | i | +---+---+---+
Ich habe die Buchstaben in die Tabelle eingefügt, um besser erklären zu können, wie das Ganze dann beim Aufruf auszusehen hat, die Zahlen entsprechen dem Zeilen- und Reihenindex.
Angenommen wir wollen hier Widget 'a' in Zelle 0/0 einfügen, dann würde der Aufruf folgendermaßen aussehen:
m_table.attach( a , 0 , 1 , 0 , 1 );
für b sieht das Ganze so aus:
m_table.attach( b , 1 , 2 , 0 , 1 );
für e sieht das Ganze dann so aus:
m_table.attach( e , 1 , 2 , 1 , 2 );
für i sieht das Ganze dann so aus:
m_table.attach( e , 2 , 3 , 2 , 3 );
Nun möchte man aber, dass sich ein Widget über mehrere Zellen erstreckt. Wie das Widget 'd' in diesem Beispiel:
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | d | +---+---+---+ 2 | e | f | g | +---+---+---+
Dann würde der das Einfügen für das Widget 'd' folgendermaßen aussehen:
m_table.attach( d , 0 , 3 , 1 , 2 );
Und für folgendes Layout
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | | + d + 2 | | +---+---+---+
so:
m_table.attach( d , 0 , 3 , 1 , 3 );
Bei den xoptions und yoptions gibt es folgende Optionen:
- FILL
- SHRINK
- EXPAND
Bei Gtk::FILL wird das Widget, sollte es kleiner als die Zelle selbst sein, auf Zellengröße vergrößert.
Gtk::EXPAND zwingt das Table zum Größer-Werden, sollte der Platz für das Widget nicht ausreichen.
Wenn die Tabelle weniger Platz bekommt als sie braucht, weil der Benutzer z.B. die Fenstergröße ändert verschwindet das Wigdet einfach. Wenn man Gtk::SHRINK angibt, werden die Widgets in der Tabelle verkleinert, damit sie in den Bereich passen.1.4 Single Widget Container
Es gibt auch Container-Widgets die nur ein einziges Widget aufnehmen können. Bestes Beispiel ist Gtk::Window.
Diese Widgets haben nur die Methode add zum Hinzufügen der Widgets und nehmen als Parameter eine Referenz auf das Widget, das hinzugefügt werden soll.
Es gibt auch noch Ausnahmen bei den "Single Widget Containern" das ist Gtk::Paned, aber auf das werde ich ein anderes Mal zu sprechen kommen
1.5 Anwendung
In dem Beispiel werde ich bereits das erste Mal Gtk::Label benutzen, um zu verdeutlichen, wie man die Boxes verwendet.
struct MyWindow : Gtk::Window { MyWindow(); Gtk::VBox m_vbox; Gtk::HBox m_hbox; Gtk::Label m_label1; Gtk::Label m_label2; Gtk::Label m_label3; }; MyWindow::MyWindow() : Gtk::Window(), m_vbox(true,5),// Homogene Ausrichtung und 5 px Abstand m_hbox(true,5),// Homogene Ausrichtung und 5 px Abstand m_label1("Label 1"), m_label2("Label 2"), m_label3("Label 3") { // Text der Titelleiste setzen set_title("GTKmm Tutorial Teil 2"); // m_label1 der horizontalen box als erstes Element übergeben m_hbox.pack_start(m_label1); // m_label2 der horizontalen box als zweites Element übergeben m_hbox.pack_start(m_label2); // m_hbox der vertikalen box als erstes Element übergeben m_vbox.pack_start(m_hbox); // m_label3 der vertikalen box als zweites Element übergeben m_vbox.pack_start(m_label3); // Die vertikale Box an das Fenster übergeben add(m_vbox); // sorgt dafür, dass alle Widgets angezeigt werden show_all_children(); }
Und so sieht's aus:
Ein Beispiel für die Verwendung eines Gtk::Table Containers findet ihr im Anwendungsbeispiel für die Buttons
2.Signale
2.1 Einführung
In GTKmm wird, um auf Signale/Events zu reagieren, die Bibliothek SigC++ verwendet. SigC++ ist sehr flexibel um mächtig und ist sehr vielseitig verwendbar. Es ist möglich, Funktionen und Methoden aller Art zu verwenden und es besteht sogar die Möglichkeit, zusätzliche Parameter an ein Signal zu binden, um zusätzliche Daten an den eigenen Signalhandler zu übergeben.
Um nicht zu ausschweifend zu werden, zeige ich euch einfach ein paar Verwendungsbeispiele:
2.2 Anwendung
2.2.1 Eine Funktion als Signalhandler
void on_button_clicked(); // Unser Signalhandler Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur Veranschaulichung button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
Wie man sieht, ist die Verwendung recht unkompliziert. Die meisten Widgets haben spezielle Zugriffsmethoden die es einem erlauben, Signalhandler zu setzen.
Gtk::Button hat in diesem Fall z.B. die Methode Gtk::Button::signal_clicked(), um den Handler zu setzen, der auf ein Klick-Ereignis reagiert.
Die Signale aller Widgets zu beschreiben, würde den Rahmen dieses Tutorials sprengen und sollten daher der GTKmm-Dokumentation entnommen werden.
2.2.2 Methoden als Signalhandler
struct test { void on_button_clicked(); // Unser Signalhandler }; Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur Veranschaulichung test test_obj; button.signal_clicked().connect(sigc::mem_fun(test_obj,&test::on_button_clicked));
Um Methoden zu binden, bietet SigC++ die Funktion sigc::mem_fun.
Als ersten Parameter nimmt sigc::mem_fun eine Referenz auf das Objekt der Methode an und als 2. Parameter den Methodenzeiger auf die Methode.
2.2.3 Binden von Parametern
Da man manchmal zusätzliche Parameter bei einem Ereignis braucht, um auf ein Signal zu reagieren, bietet SigC++ die Funktion sigc::bind an, um zusätzliche Parameter an den Signalhandler zu übergeben.
struct data {}; struct test { void on_button_clicked(data d); }; Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur Veranschaulichung test test_obj; data d; button.signal_clicked().connect( sigc::bind<data>( sigc::mem_fun( test_obj , &test::on_button_clicked ), d ) );
3.Buttons
3.1 Einführung
In GTKmm gibt es neben den "normalen" Buttons auch RadioButtons, CheckButtons (aka CheckBox) und so genannte ToggleButtons. Viel zu erzählen gibt es hier nicht.
Schaut euch einfach mal das Beispiel an und ihr seht, dass die Verwendung sehr simpel ist. In diesem Beispiel kann man auch schön die Verwendung der oben besprochenen Parameterbindung sehen. Des Weiteren verwende ich ein Gtk::Table als Container, um dessen Verwendung auch darzustellen.
3.2 Anwendung
struct MyWindow : Gtk::Window { MyWindow(); private: Gtk::ToggleButton m_toggle_button; Gtk::CheckButton m_check_button1; Gtk::CheckButton m_check_button2; Gtk::RadioButtonGroup m_radiogroup; Gtk::RadioButton m_radio_button1; Gtk::RadioButton m_radio_button2; Gtk::Button m_button; Gtk::Table m_table; private: void attach_widgets_to_table(); void connect_signals(); void on_toggle_button_clicked(); void on_radio_button_clicked(int); void on_check_button_clicked(int,Gtk::CheckButton const *); void on_button_clicked(); }; MyWindow::MyWindow() : Gtk::Window(), m_toggle_button("Gtk::ToggleButton"), m_check_button1("Gtk::CheckButton 1"), m_check_button2("Gtk::CheckButton 2"), m_radiogroup(), m_radio_button1(m_radiogroup,"Gtk::RadioButton 1"),// RadioButton einer Gruppe zuordnen und Beschriftung geben m_radio_button2(m_radiogroup,"Gtk::RadioButton 2"),// RadioButton einer Gruppe zuordnen und Beschriftung geben m_button("Gtk::Button"), m_table(3,2,true) // 3 Zeilen , 2 Spalten , Homogene aufteilung der Zellen { // Standard-Fenstergröße setzen; Breite: 400px Höhe: 170px set_default_size(400,170); // Titel setzen set_title("GTKmm Tutorial Teil 2"); // Widgets in das Table einfügen attach_widgets_to_table(); // Signale verbinden connect_signals(); // Tabelle dem Fenster übergeben add(m_table); // Alle Widgets anzeigen show_all_children(); } void MyWindow::attach_widgets_to_table() { // Button in Zelle(0,0) einfügen m_table.attach(m_button,0,1,0,1); // ToggleButton in Zelle(1,0) einfügen m_table.attach(m_toggle_button,1,2,0,1); // CheckButton in Zelle(0,1) einfügen m_table.attach(m_check_button1,0,1,1,2); // CheckButton in Zelle(1,1) einfügen m_table.attach(m_check_button2,1,2,1,2); // RadioButton in Zelle(0,2) einfügen m_table.attach(m_radio_button1,0,1,2,3); // RadioButton in Zelle(1,2) einfügen m_table.attach(m_radio_button2,1,2,2,3); } void MyWindow::connect_signals() { // Signale verbinden: m_button.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button_clicked)); m_toggle_button.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_toggle_button_clicked)); m_radio_button1.signal_clicked().connect(sigc::bind<int>(sigc::mem_fun(*this,&MyWindow::on_radio_button_clicked),1)); m_radio_button2.signal_clicked().connect(sigc::bind<int>(sigc::mem_fun(*this,&MyWindow::on_radio_button_clicked),2)); m_check_button1.signal_clicked().connect(sigc::bind<int,Gtk::CheckButton const *>(sigc::mem_fun(*this,&MyWindow::on_check_button_clicked),1,&m_check_button1)); m_check_button2.signal_clicked().connect(sigc::bind<int,Gtk::CheckButton const *>(sigc::mem_fun(*this,&MyWindow::on_check_button_clicked),2,&m_check_button2)); } void MyWindow::on_toggle_button_clicked() { Glib::ustring msg = "ToggleButton wurde angeklickt. Neuer Status ist: "; if(m_toggle_button.get_active()) msg += "gedrueckt"; else msg += "nicht gedrueckt"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_radio_button_clicked(int which) { Glib::ustring msg = "Der Status von RadioButton "; if(which == 1) msg += "1 hat sich geaendert"; else msg += "2 hat sich geaendert"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_check_button_clicked(int which, Gtk::CheckButton const * cb) { if(!cb) { Gtk::MessageDialog dia(*this,"Es ist ein Fehler aufgetreten",false,Gtk::MESSAGE_ERROR); dia.run(); return; } Glib::ustring msg = "CheckButton "; if(which == 1) msg += "1"; else msg += "2"; if(cb->get_active()) msg += " markiert"; else msg += " Markierung aufgehoben"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_button_clicked() { Gtk::MessageDialog dia(*this,"Button wurde angeklickt :)"); dia.run(); } int main(int argc, char **argv) { Gtk::Main main(argc,argv); MyWindow window; main.run(window); return 0; }
Und so sieht's aus:
4.Implementation eines weiteren Beispiels
#include <gtkmm.h> struct MyWindow : Gtk::Window { MyWindow(); ~MyWindow(); void on_button1_clicked(); void on_button2_clicked(); Gtk::Button m_button1; Gtk::Button m_button2; Gtk::Label m_label; Gtk::VBox m_vbox; Gtk::HBox m_hbox; }; MyWindow::MyWindow() : Gtk::Window(), m_button1("Klick mich1"), m_button2("Klick mich2"), m_label("<u><i><b>Ich bin ein Label</b></i></u>"), m_vbox(true,5), m_hbox(true,5) { set_title("GTKmm Tutorial Teil 2"); m_label.set_use_markup(true); m_hbox.pack_start(m_button1); m_hbox.pack_end(m_button2); m_button1.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button1_clicked)); m_button2.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button2_clicked)); m_vbox.pack_start(m_hbox); m_vbox.pack_start(m_label); add(m_vbox); set_default_size(200,100); show_all_children(true); } MyWindow::~MyWindow() { } void MyWindow::on_button1_clicked() { m_label.set_markup("<b><i>Button 1</i> wurde angeklickt</b>"); m_label.set_use_markup(true); } void MyWindow::on_button2_clicked() { m_label.set_markup("<b><u>Button 2</u> wurde angeklickt</b>"); m_label.set_use_markup(true); } int main(int argc, char **argv) { Gtk::Main main(argc,argv); MyWindow window; main.run(window); return 0; }
Und last but not least: So sieht's aus:
So, diesmal gab's viel Code und wenig Erklärungen, so wird es auch im nächsten Teil aussehen, da ich nicht glaube, dass es zu den Widgets viel zu erzählen gibt. Ich hoffe, dass dieses Tutorial hilfreich war.
Ich schmeiße mich gleich auch ans nächste Tutorial damit da nicht wieder so ne Ewigkeit dazwischen liegt
BR
evilissimo
-
Hi
bin mit der Korrektur soweit fertig.
In dem ersten Code (Abschnitt 1.5) waren, glaube ich, Copy&Paste-Fehler in den Kommentaren:// m_label1 der horizontalen box als erstes element übergeben m_hbox.pack_start(m_label1); // m_label1 der horizontalen box als zweites element übergeben <-- wohl eher m_label2, oder? m_hbox.pack_start(m_label2); // m_hbox der vertikalen box als erstes element übergeben m_vbox.pack_start(m_hbox); // m_hbox der vertikalen box als zweites element übergeben <-- wohl eher m_label3, oder? m_vbox.pack_start(m_label3);
Ich habs jedenfalls mal so verbessert.
PS: Find ich gut, dass du mal wieder weiterschreibst, ist echt interessant
-
Grafische Benutzerschnittstellen in C++ mit GTKmm Betriebssystemunabhängig gestalten Teil 2
Nach einer langen Auszeit habe ich mich dazu aufgerafft, endlich am Tutorial weiter zu schreiben. Ich hoffe, ihr könnt mir diese Pause verzeihen
Wie versprochen behandele ich in diesem Teil das GTK+ Boxensystem, Labels, Buttons und Signale (Events). Ich werde ein paar Anwendungsbeispiele zu dem jeweiligen Thema aufzeigen. Zum Schluss werde ich ein Beispiel mit allen Widgets zeigen und die eventuell noch unbekannten Schritte zusätzlich kommentieren.
1.GTK+ Boxensystem
1.1 Einführung
In GTK+ und somit auch in GTKmm werden die Widgets mit so genannten „boxes“ angeordnet. Das sind Container, die die Widgets aufnehmen können. Mit diesem Konzept ordnet man Widgets nur relativ und nicht absolut an, im Gegensatz zu dem wie man es von den bekannteren GUI-Designern kennt (z.b. Borland C++ Builder, Visual C++, Visual C# usw.)
Dieses Konzept ermöglicht ein dynamisches Anpassen der Größe der Widgets zur jeweiligen Fenstergröße, ohne das sich der Programmierer darum kümmern müsste.
Von diesen Containern gibt es verschiedene Arten.
Es gibt Container die nur jeweils ein Widget aufnehmen können ( diese wurden von Gtk::Bin abgeleitet ), Container die mehrere Widgets aufnehmen können, aber nur in eine Richtung, sprich Horizontal oder Vertikal. Dies wären z.B. Gtk::Hbox für die horizontale und Gtk::VBox für die vertikale Richtung.
Und zu guter Letzt gibt es noch eine Tabelle ( Gtk::Table ) darin kann man n x m Widgets unterbringen je nach dem wie man es braucht, eben in einer tabellarischen Anordnung.Eine Besonderheit ist aber das Widget Gtk::Fixed. Dies kann Elemente an festen Koordinaten aufnehmen, welche dann aber nicht resizable sind.
Ich werde auf diese Widgets nicht näher eingehen, da ich der Meinung bin, dass man auf diese auch verzichten kann und mir das Programmieren mit dem Boxensystem wesentlich einfacher und eleganter erscheint.1.2 Widgets und Boxes
Die Container Gtk::HBox und Gtk::VBox haben neben dem Standard-Konstruktor einen Konstruktor mit folgenden Parametern:
Gtk::HBox( bool homogenous , int padding ); Gtk::VBox( bool homogenous , int padding );
homogenous gibt hier an, dass der Platz der Widgets gleichmäßig aufgeteilt wird wenn man true übergibt. Der Parameter padding gibt an wie viel Platz in Pixeln zwischen den Bereichen der Widgets sein soll.
Um Widgets in die Boxes einzufügen, haben diese folgende Methoden:
void pack_start(Gtk::Widget& child, PackOptions options = PACK_EXPAND_WIDGET, guint padding = 0); void pack_end(Gtk::Widget& child, PackOptions options = PACK_EXPAND_WIDGET, guint padding = 0);
Der erste Parameter ist eine Referenz auf das Widget, das wir hinzufügen wollen, z.B. ein Gtk::Button Objekt.
Der zweite Parameter gibt die PackOption an. Davon hängt es ab, wie sich die Anordnung der Widgets abspielt.
Es gibt 3 verschiedene Optionen:
- PACK_SHRINK
- PACK_EXPAND_WIDGET
- PACK_EXPAND_PADDING
Gibt man PACK_SHRINK an, wird nur so viel Platz verwendet, wie das Widget wirklich braucht und es wird niemals expandiert.
Bei PACK_EXPAND_WIDGET wird der freie Platz ausgefüllt in dem das Widget einfach vergrößert wird.
Bei PACK_EXPAND_PADDING wird der freie Platz einfach durch abstände ausgefüllt, das Widget ist dann so gesehen zentriert in seinem Bereich. Der Bereich ist abhängig von dem Drumherum und das wird alles immer dynamisch angepasst.
Der dritte Parameter padding gibt an wie viel Platz das Widget drum herum haben soll. Im Gegensatz zu dem Parameter des Konstruktors ist hier der Platz um das Widget herum gemeint.
1.3 Widgets und Tables
Gtk::Table hat folgenden Konstruktor:
Gtk::Table::Table(guint n_rows = 1, guint n_columns = 1, bool homogeneous = false)
Im Grunde ist dieser eigentlich selbsterklärend. n_rows gibt an, wie viele Zeilen das Table haben soll und n_columns wie viele Spalten.
homogenous gibt an ob die Zellen homogen, sprich immer gleich groß sein sollen.
Generell wäre ich vorsichtig mit der Verwendung von homogenous = true, da es viele Widgets gibt, die sehr viel Platz brauchen und es damit eine negative Auswirkung auf alle Widgets haben kann. Aber natürlich kann dieser Effekt auch erwünscht sein.
Das Beste ist, dass man sich generell erst mal mit den ganzen Containern auseinander setzt und damit etwas rumspielt, bis man das alles verstanden hat.
Das ist einfacher, als ellenlange Texte darüber zu lesen. Die eigene Erfahrung ist immer noch die lehrreichste. (Das ist meine Meinung )So und nun möchte ich euch erstmal schocken:
void Gtk::Table::attach(Gtk::Widget& child, guint left_attach, guint right_attach, guint top_attach, guint bottom_attach, guint xoptions = Gtk::FILL | Gtk::EXPAND, guint yoptions = Gtk::FILL | Gtk::EXPAND, guint xpadding = 0, guint ypadding = 0);
Diese Methode ist natürlich auf den ersten Blick ziemlich abschreckend, aber die Verwendung ist einfacher, als es auf den ersten Blick aussehen mag.
child ist das Widget, das wir einfügen möchten und als Referenz auf das Widget-Objekt übergeben wird
left_attach gibt an, welche Zelle die Startposition auf der X-Achse ist
right_attach gibt an, welche Zelle die Endposition auf der X-Achse ist, wobei die Endposition mindestens 1 größer als die Startposition sein muss
top_attach gibt an, welche Zelle die Startposition auf der Y-Achse ist
bottom_attach gibt an, welche Zelle die Endposition auf der Y-Achse ist, wobei die Endposition mindestens 1 größer als die Startposition sein mussxoptions gibt an, wie sich das Widget innerhalb seiner Zelle auf der X-Achse verhalten soll. Dies ist ähnlich den PackOptions bei den Boxes
yoptions gibt an, wie sich das Widget innerhalb seiner Zelle auf der Y-Achse verhalten soll. Dies ist ähnlich den PackOptions bei den Boxesxpadding gibt an, wie viel Platz das Widget links und rechts von sich haben soll (freier Platz)
xpadding gibt an, wie viel Platz das Widget oben und unten von sich haben soll (freier Platz)Um das Ganze zu verstehen, schauen wir uns einfach mal den Aufbau einer Tabelle an:
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | d | e | f | +---+---+---+ 2 | g | h | i | +---+---+---+
Ich habe die Buchstaben in die Tabelle eingefügt, um besser erklären zu können, wie das Ganze dann beim Aufruf auszusehen hat, die Zahlen entsprechen dem Zeilen- und Reihenindex.
Angenommen wir wollen hier Widget 'a' in Zelle 0/0 einfügen, dann würde der Aufruf folgendermaßen aussehen:
m_table.attach( a , 0 , 1 , 0 , 1 );
für b sieht das Ganze so aus:
m_table.attach( b , 1 , 2 , 0 , 1 );
für e sieht das Ganze dann so aus:
m_table.attach( e , 1 , 2 , 1 , 2 );
für i sieht das Ganze dann so aus:
m_table.attach( i , 2 , 3 , 2 , 3 );
Nun möchte man aber, dass sich ein Widget über mehrere Zellen erstreckt. Wie das Widget 'd' in diesem Beispiel:
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | d | +---+---+---+ 2 | e | f | g | +---+---+---+
Dann würde der das Einfügen für das Widget 'd' folgendermaßen aussehen:
m_table.attach( d , 0 , 3 , 1 , 2 );
Und für folgendes Layout
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | | + d + 2 | | +---+---+---+
so:
m_table.attach( d , 0 , 3 , 1 , 3 );
Bei den xoptions und yoptions gibt es folgende Optionen:
- FILL
- SHRINK
- EXPAND
Bei Gtk::FILL wird das Widget, sollte es kleiner als die Zelle selbst sein, auf Zellengröße vergrößert.
Gtk::EXPAND zwingt das Table zum Größer-Werden, sollte der Platz für das Widget nicht ausreichen.
Wenn die Tabelle weniger Platz bekommt als sie braucht, weil der Benutzer z.B. die Fenstergröße ändert verschwindet das Wigdet einfach. Wenn man Gtk::SHRINK angibt, werden die Widgets in der Tabelle verkleinert, damit sie in den Bereich passen.1.4 Single Widget Container
Es gibt auch Container-Widgets die nur ein einziges Widget aufnehmen können. Bestes Beispiel ist Gtk::Window.
Diese Widgets haben nur die Methode add zum Hinzufügen der Widgets und nehmen als Parameter eine Referenz auf das Widget, das hinzugefügt werden soll.
Es gibt auch noch Ausnahmen bei den "Single Widget Containern" das ist Gtk::Paned, aber auf das werde ich ein anderes Mal zu sprechen kommen
1.5 Anwendung
In dem Beispiel werde ich bereits das erste Mal Gtk::Label benutzen, um zu verdeutlichen, wie man die Boxes verwendet.
struct MyWindow : Gtk::Window { MyWindow(); Gtk::VBox m_vbox; Gtk::HBox m_hbox; Gtk::Label m_label1; Gtk::Label m_label2; Gtk::Label m_label3; }; MyWindow::MyWindow() : Gtk::Window(), m_vbox(true,5),// Homogene Ausrichtung und 5 px Abstand m_hbox(true,5),// Homogene Ausrichtung und 5 px Abstand m_label1("Label 1"), m_label2("Label 2"), m_label3("Label 3") { // Text der Titelleiste setzen set_title("GTKmm Tutorial Teil 2"); // m_label1 der horizontalen box als erstes Element übergeben m_hbox.pack_start(m_label1); // m_label2 der horizontalen box als zweites Element übergeben m_hbox.pack_start(m_label2); // m_hbox der vertikalen box als erstes Element übergeben m_vbox.pack_start(m_hbox); // m_label3 der vertikalen box als zweites Element übergeben m_vbox.pack_start(m_label3); // Die vertikale Box an das Fenster übergeben add(m_vbox); // sorgt dafür, dass alle Widgets angezeigt werden show_all_children(); }
Und so sieht's aus:
Ein Beispiel für die Verwendung eines Gtk::Table Containers findet ihr im Anwendungsbeispiel für die Buttons
2.Signale
2.1 Einführung
In GTKmm wird, um auf Signale/Events zu reagieren, die Bibliothek SigC++ verwendet. SigC++ ist sehr flexibel um mächtig und ist sehr vielseitig verwendbar. Es ist möglich, Funktionen und Methoden aller Art zu verwenden und es besteht sogar die Möglichkeit, zusätzliche Parameter an ein Signal zu binden, um zusätzliche Daten an den eigenen Signalhandler zu übergeben.
Um nicht zu ausschweifend zu werden, zeige ich euch einfach ein paar Verwendungsbeispiele:
2.2 Anwendung
2.2.1 Eine Funktion als Signalhandler
void on_button_clicked(); // Unser Signalhandler Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur Veranschaulichung button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
Wie man sieht, ist die Verwendung recht unkompliziert. Die meisten Widgets haben spezielle Zugriffsmethoden die es einem erlauben, Signalhandler zu setzen.
Gtk::Button hat in diesem Fall z.B. die Methode Gtk::Button::signal_clicked(), um den Handler zu setzen, der auf ein Klick-Ereignis reagiert.
Die Signale aller Widgets zu beschreiben, würde den Rahmen dieses Tutorials sprengen und sollten daher der GTKmm-Dokumentation entnommen werden.
2.2.2 Methoden als Signalhandler
struct test { void on_button_clicked(); // Unser Signalhandler }; Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur Veranschaulichung test test_obj; button.signal_clicked().connect(sigc::mem_fun(test_obj,&test::on_button_clicked));
Um Methoden zu binden, bietet SigC++ die Funktion sigc::mem_fun.
Als ersten Parameter nimmt sigc::mem_fun eine Referenz auf das Objekt der Methode an und als 2. Parameter den Methodenzeiger auf die Methode.
2.2.3 Binden von Parametern
Da man manchmal zusätzliche Parameter bei einem Ereignis braucht, um auf ein Signal zu reagieren, bietet SigC++ die Funktion sigc::bind an, um zusätzliche Parameter an den Signalhandler zu übergeben.
struct data {}; struct test { void on_button_clicked(data d); }; Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur Veranschaulichung test test_obj; data d; button.signal_clicked().connect( sigc::bind<data>( sigc::mem_fun( test_obj , &test::on_button_clicked ), d ) );
3.Buttons
3.1 Einführung
In GTKmm gibt es neben den "normalen" Buttons auch RadioButtons, CheckButtons (aka CheckBox) und so genannte ToggleButtons. Viel zu erzählen gibt es hier nicht.
Schaut euch einfach mal das Beispiel an und ihr seht, dass die Verwendung sehr simpel ist. In diesem Beispiel kann man auch schön die Verwendung der oben besprochenen Parameterbindung sehen. Des Weiteren verwende ich ein Gtk::Table als Container, um dessen Verwendung auch darzustellen.
3.2 Anwendung
struct MyWindow : Gtk::Window { MyWindow(); private: Gtk::ToggleButton m_toggle_button; Gtk::CheckButton m_check_button1; Gtk::CheckButton m_check_button2; Gtk::RadioButtonGroup m_radiogroup; Gtk::RadioButton m_radio_button1; Gtk::RadioButton m_radio_button2; Gtk::Button m_button; Gtk::Table m_table; private: void attach_widgets_to_table(); void connect_signals(); void on_toggle_button_clicked(); void on_radio_button_clicked(int); void on_check_button_clicked(int,Gtk::CheckButton const *); void on_button_clicked(); }; MyWindow::MyWindow() : Gtk::Window(), m_toggle_button("Gtk::ToggleButton"), m_check_button1("Gtk::CheckButton 1"), m_check_button2("Gtk::CheckButton 2"), m_radiogroup(), m_radio_button1(m_radiogroup,"Gtk::RadioButton 1"),// RadioButton einer Gruppe zuordnen und Beschriftung geben m_radio_button2(m_radiogroup,"Gtk::RadioButton 2"),// RadioButton einer Gruppe zuordnen und Beschriftung geben m_button("Gtk::Button"), m_table(3,2,true) // 3 Zeilen , 2 Spalten , Homogene aufteilung der Zellen { // Standard-Fenstergröße setzen; Breite: 400px Höhe: 170px set_default_size(400,170); // Titel setzen set_title("GTKmm Tutorial Teil 2"); // Widgets in das Table einfügen attach_widgets_to_table(); // Signale verbinden connect_signals(); // Tabelle dem Fenster übergeben add(m_table); // Alle Widgets anzeigen show_all_children(); } void MyWindow::attach_widgets_to_table() { // Button in Zelle(0,0) einfügen m_table.attach(m_button,0,1,0,1); // ToggleButton in Zelle(1,0) einfügen m_table.attach(m_toggle_button,1,2,0,1); // CheckButton in Zelle(0,1) einfügen m_table.attach(m_check_button1,0,1,1,2); // CheckButton in Zelle(1,1) einfügen m_table.attach(m_check_button2,1,2,1,2); // RadioButton in Zelle(0,2) einfügen m_table.attach(m_radio_button1,0,1,2,3); // RadioButton in Zelle(1,2) einfügen m_table.attach(m_radio_button2,1,2,2,3); } void MyWindow::connect_signals() { // Signale verbinden: m_button.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button_clicked)); m_toggle_button.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_toggle_button_clicked)); m_radio_button1.signal_clicked().connect(sigc::bind<int>(sigc::mem_fun(*this,&MyWindow::on_radio_button_clicked),1)); m_radio_button2.signal_clicked().connect(sigc::bind<int>(sigc::mem_fun(*this,&MyWindow::on_radio_button_clicked),2)); m_check_button1.signal_clicked().connect(sigc::bind<int,Gtk::CheckButton const *>(sigc::mem_fun(*this,&MyWindow::on_check_button_clicked),1,&m_check_button1)); m_check_button2.signal_clicked().connect(sigc::bind<int,Gtk::CheckButton const *>(sigc::mem_fun(*this,&MyWindow::on_check_button_clicked),2,&m_check_button2)); } void MyWindow::on_toggle_button_clicked() { Glib::ustring msg = "ToggleButton wurde angeklickt. Neuer Status ist: "; if(m_toggle_button.get_active()) msg += "gedrueckt"; else msg += "nicht gedrueckt"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_radio_button_clicked(int which) { Glib::ustring msg = "Der Status von RadioButton "; if(which == 1) msg += "1 hat sich geaendert"; else msg += "2 hat sich geaendert"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_check_button_clicked(int which, Gtk::CheckButton const * cb) { if(!cb) { Gtk::MessageDialog dia(*this,"Es ist ein Fehler aufgetreten",false,Gtk::MESSAGE_ERROR); dia.run(); return; } Glib::ustring msg = "CheckButton "; if(which == 1) msg += "1"; else msg += "2"; if(cb->get_active()) msg += " markiert"; else msg += " Markierung aufgehoben"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_button_clicked() { Gtk::MessageDialog dia(*this,"Button wurde angeklickt :)"); dia.run(); } int main(int argc, char **argv) { Gtk::Main main(argc,argv); MyWindow window; main.run(window); return 0; }
Und so sieht's aus:
4.Implementation eines weiteren Beispiels
#include <gtkmm.h> struct MyWindow : Gtk::Window { MyWindow(); ~MyWindow(); void on_button1_clicked(); void on_button2_clicked(); Gtk::Button m_button1; Gtk::Button m_button2; Gtk::Label m_label; Gtk::VBox m_vbox; Gtk::HBox m_hbox; }; MyWindow::MyWindow() : Gtk::Window(), m_button1("Klick mich1"), m_button2("Klick mich2"), m_label("<u><i><b>Ich bin ein Label</b></i></u>"), m_vbox(true,5), m_hbox(true,5) { set_title("GTKmm Tutorial Teil 2"); m_label.set_use_markup(true); m_hbox.pack_start(m_button1); m_hbox.pack_end(m_button2); m_button1.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button1_clicked)); m_button2.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button2_clicked)); m_vbox.pack_start(m_hbox); m_vbox.pack_start(m_label); add(m_vbox); set_default_size(200,100); show_all_children(true); } MyWindow::~MyWindow() { } void MyWindow::on_button1_clicked() { m_label.set_markup("<b><i>Button 1</i> wurde angeklickt</b>"); m_label.set_use_markup(true); } void MyWindow::on_button2_clicked() { m_label.set_markup("<b><u>Button 2</u> wurde angeklickt</b>"); m_label.set_use_markup(true); } int main(int argc, char **argv) { Gtk::Main main(argc,argv); MyWindow window; main.run(window); return 0; }
Und last but not least: So sieht's aus:
So, diesmal gab's viel Code und wenig Erklärungen, so wird es auch im nächsten Teil aussehen, da ich nicht glaube, dass es zu den Widgets viel zu erzählen gibt. Ich hoffe, dass dieses Tutorial hilfreich war.
Ich schmeiße mich gleich auch ans nächste Tutorial damit da nicht wieder so ne Ewigkeit dazwischen liegt
BR
evilissimo
-
Ich hab nur ein paar style infos hinzugefügt und bei nem code ein typo gefixt. Ich hab das ganze übernommen wie predator das korrigiert hat
Wenn noch jemand mal drüber gucken möchte dann tut das, ansonsten kann das dann auf [E]
BR
-
Grafische Benutzerschnittstellen in C++ mit GTKmm betriebssystemunabhängig gestalten Teil 2
Nach einer langen Auszeit habe ich mich dazu aufgerafft, endlich am Tutorial weiterzuschreiben. Ich hoffe, ihr könnt mir diese Pause verzeihen.
Wie versprochen behandle ich in diesem Teil das GTK+ Boxensystem, Labels, Buttons und Signale (Events). Ich werde ein paar Anwendungsbeispiele zu dem jeweiligen Thema anführen. Zum Schluss werde ich ein Beispiel mit allen Widgets zeigen und die eventuell noch unbekannten Schritte zusätzlich kommentieren.
1.GTK+ Boxensystem
1.1 Einführung
In GTK+ und somit auch in GTKmm werden die Widgets mit so genannten „boxes“ angeordnet. Das sind Container, die die Widgets aufnehmen können. Mit diesem Konzept ordnet man Widgets nur relativ und nicht absolut an - im Gegensatz zu dem, wie man es von den bekannteren GUI-Designern kennt (z.b. Borland C++ Builder, Visual C++, Visual C# usw.)
Dieses Konzept ermöglicht ein dynamisches Anpassen der Größe der Widgets zur jeweiligen Fenstergröße, ohne das sich der Programmierer darum kümmern müsste.
Von diesen Containern gibt es verschiedene Arten.
Es gibt Container***,*** die nur jeweils ein Widget aufnehmen können ( diese wurden von Gtk::Bin abgeleitet ), Container die mehrere Widgets aufnehmen können, aber nur in eine Richtung, sprich Horizontal oder Vertikal. Dies wären z.B. Gtk::Hbox für die horizontale und Gtk::VBox für die vertikale Richtung.
Und zu guter Letzt gibt es noch eine Tabelle ( Gtk::Table )***, in der man n x m Widgets, je nach dem, wie man es braucht, unterbringen kann,*** eben in einer tabellarischen Anordnung.Eine Besonderheit ist aber das Widget Gtk::Fixed. Dies kann Elemente an festen Koordinaten aufnehmen, welche dann aber nicht resizable sind.
Ich werde auf diese Widgets nicht näher eingehen, da ich der Meinung bin, dass man auf diese auch verzichten kann***,*** und mir das Programmieren mit dem Boxensystem wesentlich einfacher und eleganter erscheint.1.2 Widgets und Boxes
Die Container Gtk::HBox und Gtk::VBox haben neben dem Standard-Konstruktor einen Konstruktor mit folgenden Parametern:
Gtk::HBox( bool homogenous , int padding ); Gtk::VBox( bool homogenous , int padding );
homogenous gibt hier an, dass der Platz der Widgets gleichmäßig aufgeteilt wird, wenn man true übergibt. Der Parameter padding gibt an, wie viel Platz in Pixeln zwischen den Bereichen der Widgets sein soll.
Um Widgets in die Boxes einzufügen, haben diese folgende Methoden:
void pack_start(Gtk::Widget& child, PackOptions options = PACK_EXPAND_WIDGET, guint padding = 0); void pack_end(Gtk::Widget& child, PackOptions options = PACK_EXPAND_WIDGET, guint padding = 0);
Der erste Parameter ist eine Referenz auf das Widget, das wir hinzufügen wollen, z.B. ein Gtk::Button Objekt.
Der zweite Parameter gibt die PackOption an. Davon hängt es ab, wie sich die Anordnung der Widgets abspielt.
Es gibt 3 verschiedene Optionen:
- PACK_SHRINK
- PACK_EXPAND_WIDGET
- PACK_EXPAND_PADDING
Gibt man PACK_SHRINK an, wird nur so viel Platz verwendet, wie das Widget wirklich braucht, und es wird niemals expandiert.
Bei PACK_EXPAND_WIDGET wird der freie Platz ausgefüllt***, indem*** das Widget einfach vergrößert wird.
Bei PACK_EXPAND_PADDING wird der freie Platz einfach durch Abständeausgefüllt; das Widget ist dann so gesehen zentriert in seinem Bereich. Der Bereich ist abhängig von dem Drumherum und das wird alles immer dynamisch angepasst.
Der dritte Parameter padding gibt an, wie viel Platz das Widget drum herum haben soll. Im Gegensatz zu dem Parameter des Konstruktors ist hier der Platz um das Widget herum gemeint.
1.3 Widgets und Tables
Gtk::Table hat folgenden Konstruktor:
Gtk::Table::Table(guint n_rows = 1, guint n_columns = 1, bool homogeneous = false)
Im Grunde ist dieser eigentlich selbsterklärend. n_rows gibt an, wie viele Zeilen das Table haben soll, und n_columns, wie viele Spalten.
homogenous gibt an, ob die Zellen homogen, sprich immer gleich groß, sein sollen.
Generell wäre ich vorsichtig mit der Verwendung von homogenous = true, da es viele Widgets gibt, die sehr viel Platz brauchen und es damit eine negative Auswirkung auf alle Widgets haben kann. Aber natürlich kann dieser Effekt auch erwünscht sein.
Das Beste ist, dass man sich generell erst mal mit den ganzen Containern auseinander setzt und damit etwas rumspielt, bis man das alles verstanden hat.
Das ist einfacher, als ellenlange Texte darüber zu lesen. Die eigene Erfahrung ist immer noch die lehrreichste. (Das ist meine Meinung )So und nun möchte ich euch erst mal schocken:
void Gtk::Table::attach(Gtk::Widget& child, guint left_attach, guint right_attach, guint top_attach, guint bottom_attach, guint xoptions = Gtk::FILL | Gtk::EXPAND, guint yoptions = Gtk::FILL | Gtk::EXPAND, guint xpadding = 0, guint ypadding = 0);
Diese Methode ist natürlich auf den ersten Blick ziemlich abschreckend, aber die Verwendung ist einfacher, als es auf den ersten Blick aussehen mag.
child ist das Widget, das wir einfügen möchten und das als Referenz auf das Widget-Objekt übergeben wird.
left_attach gibt an, welche Zelle die Startposition auf der X-Achse ist.
right_attach gibt an, welche Zelle die Endposition auf der X-Achse ist, wobei die Endposition mindestens 1 größer als die Startposition sein muss.
top_attach gibt an, welche Zelle die Startposition auf der Y-Achse ist.
bottom_attach gibt an, welche Zelle die Endposition auf der Y-Achse ist, wobei die Endposition mindestens 1 größer als die Startposition sein muss.xoptions gibt an, wie sich das Widget innerhalb seiner Zelle auf der X-Achse verhalten soll. Dies ist den PackOptions bei den Boxes ähnlich.
yoptions gibt an, wie sich das Widget innerhalb seiner Zelle auf der Y-Achse verhalten soll. Dies ist den PackOptions bei den Boxes ähnlich.xpadding gibt an, wie viel Platz das Widget links und rechts von sich haben soll (freier Platz).
xpadding gibt an, wie viel Platz das Widget oben und unten von sich haben soll (freier Platz).Um das Ganze zu verstehen, schauen wir uns einfach mal den Aufbau einer Tabelle an:
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | d | e | f | +---+---+---+ 2 | g | h | i | +---+---+---+
Ich habe die Buchstaben in die Tabelle eingefügt, um besser erklären zu können, wie das Ganze dann beim Aufruf auszusehen hat. Die Zahlen entsprechen dem Zeilen- und Reihenindex.
Angenommen, wir wollen hier Widget 'a' in Zelle 0/0 einfügen, dann würde der Aufruf folgendermaßen aussehen:
m_table.attach( a , 0 , 1 , 0 , 1 );
für b sieht das Ganze so aus:
m_table.attach( b , 1 , 2 , 0 , 1 );
für e sieht das Ganze dann so aus:
m_table.attach( e , 1 , 2 , 1 , 2 );
für i sieht das Ganze dann so aus:
m_table.attach( i , 2 , 3 , 2 , 3 );
Nun möchte man aber, dass sich ein Widget über mehrere Zellen erstreckt. Wie das Widget 'd' in diesem Beispiel:
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | d | +---+---+---+ 2 | e | f | g | +---+---+---+
Dann würde das Einfügen für das Widget 'd' folgendermaßen aussehen:
m_table.attach( d , 0 , 3 , 1 , 2 );
Und für folgendes Layout
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | | + d + 2 | | +---+---+---+
so:
m_table.attach( d , 0 , 3 , 1 , 3 );
Bei den xoptions und yoptions gibt es folgende Optionen:
- FILL
- SHRINK
- EXPAND
Bei Gtk::FILL wird das Widget, sollte es kleiner als die Zelle selbst sein, auf Zellengröße vergrößert.
Gtk::EXPAND zwingt das Table zum Größerwerden, sollte der Platz für das Widget nicht ausreichen.
Wenn die Tabelle weniger Platz bekommt, als sie braucht, weil der Benutzer z.B. die Fenstergröße ändert, verschwindet das Wigdet einfach. Wenn man Gtk::SHRINK angibt, werden die Widgets in der Tabelle verkleinert, damit sie in den Bereich passen.1.4 Single Widget Container
Es gibt auch Container-Widgets, die nur ein einziges Widget aufnehmen können. Bestes Beispiel ist Gtk::Window.
Diese Widgets haben nur die Methode add zum Hinzufügen der Widgets und nehmen als Parameter eine Referenz auf das Widget, das hinzugefügt werden soll.
Es gibt auch noch Ausnahmen bei den "Single Widget Containern": Das ist Gtk::Paned, aber auf das werde ich ein anderes Mal zu sprechen kommen
1.5 Anwendung
In dem Beispiel werde ich bereits das erste Mal Gtk::Label benutzen, um zu verdeutlichen, wie man die Boxes verwendet.
struct MyWindow : Gtk::Window { MyWindow(); Gtk::VBox m_vbox; Gtk::HBox m_hbox; Gtk::Label m_label1; Gtk::Label m_label2; Gtk::Label m_label3; }; MyWindow::MyWindow() : Gtk::Window(), m_vbox(true,5),// homogene Ausrichtung und 5 px Abstand m_hbox(true,5),// homogene Ausrichtung und 5 px Abstand m_label1("Label 1"), m_label2("Label 2"), m_label3("Label 3") { // Text der Titelleiste setzen set_title("GTKmm Tutorial Teil 2"); // m_label1 der horizontalen box als erstes Element übergeben m_hbox.pack_start(m_label1); // m_label2 der horizontalen box als zweites Element übergeben m_hbox.pack_start(m_label2); // m_hbox der vertikalen box als erstes Element übergeben m_vbox.pack_start(m_hbox); // m_label3 der vertikalen box als zweites Element übergeben m_vbox.pack_start(m_label3); // die vertikale Box an das Fenster übergeben add(m_vbox); // sorgt dafür, dass alle Widgets angezeigt werden show_all_children(); }
Und so sieht's aus:
Ein Beispiel für die Verwendung eines Gtk::Table Containers findet ihr im Anwendungsbeispiel für die Buttons
2.Signale
2.1 Einführung
In GTKmm wird, um auf Signale/Events zu reagieren, die Bibliothek SigC++ verwendet. SigC++ ist sehr flexibel und mächtig und ist sehr vielseitig verwendbar. Es ist möglich, Funktionen und Methoden aller Art zu verwenden, und es besteht sogar die Möglichkeit, zusätzliche Parameter an ein Signal zu binden, um zusätzliche Daten an den eigenen Signalhandler zu übergeben.
Um nicht zu ausschweifend zu werden, zeige ich euch einfach ein paar Verwendungsbeispiele:
2.2 Anwendung
2.2.1 Eine Funktion als Signalhandler
void on_button_clicked(); // Unser Signalhandler Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur Veranschaulichung button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
Wie man sieht, ist die Verwendung recht unkompliziert. Die meisten Widgets haben spezielle Zugriffsmethoden, die es einem erlauben, Signalhandler zu setzen.
Gtk::Button hat in diesem Fall z.B. die Methode Gtk::Button::signal_clicked(), um den Handler zu setzen, der auf ein Klick-Ereignis reagiert.
Die Signale aller Widgets zu beschreiben, würde den Rahmen dieses Tutorials sprengen***. Sie*** sollten daher der GTKmm-Dokumentation entnommen werden.
2.2.2 Methoden als Signalhandler
struct test { void on_button_clicked(); // Unser Signalhandler }; Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur Veranschaulichung test test_obj; button.signal_clicked().connect(sigc::mem_fun(test_obj,&test::on_button_clicked));
Um Methoden zu binden, bietet SigC++ die Funktion sigc::mem_fun.
Als ersten Parameter nimmt sigc::mem_fun eine Referenz auf das Objekt der Methode und als zweiten Parameter den Methodenzeiger auf die Methode an.
2.2.3 Binden von Parametern
Da man manchmal zusätzliche Parameter bei einem Ereignis braucht, um auf ein Signal zu reagieren, bietet SigC++ die Funktion sigc::bind an, um zusätzliche Parameter an den Signalhandler zu übergeben.
struct data {}; struct test { void on_button_clicked(data d); }; Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur Veranschaulichung test test_obj; data d; button.signal_clicked().connect( sigc::bind<data>( sigc::mem_fun( test_obj , &test::on_button_clicked ), d ) );
3.Buttons
3.1 Einführung
In GTKmm gibt es neben den "normalen" Buttons auch RadioButtons, CheckButtons (aka CheckBox) und so genannte ToggleButtons. Viel zu erzählen gibt es hier nicht.
Schaut euch einfach mal das Beispiel an und ihr seht, dass die Verwendung sehr simpel ist. In diesem Beispiel kann man auch schön die Verwendung der oben besprochenen Parameterbindung sehen. Des Weiteren verwende ich ein Gtk::Table als Container, um dessen Verwendung auch darzustellen.
3.2 Anwendung
struct MyWindow : Gtk::Window { MyWindow(); private: Gtk::ToggleButton m_toggle_button; Gtk::CheckButton m_check_button1; Gtk::CheckButton m_check_button2; Gtk::RadioButtonGroup m_radiogroup; Gtk::RadioButton m_radio_button1; Gtk::RadioButton m_radio_button2; Gtk::Button m_button; Gtk::Table m_table; private: void attach_widgets_to_table(); void connect_signals(); void on_toggle_button_clicked(); void on_radio_button_clicked(int); void on_check_button_clicked(int,Gtk::CheckButton const *); void on_button_clicked(); }; MyWindow::MyWindow() : Gtk::Window(), m_toggle_button("Gtk::ToggleButton"), m_check_button1("Gtk::CheckButton 1"), m_check_button2("Gtk::CheckButton 2"), m_radiogroup(), m_radio_button1(m_radiogroup,"Gtk::RadioButton 1"),// RadioButton einer Gruppe zuordnen und Beschriftung geben m_radio_button2(m_radiogroup,"Gtk::RadioButton 2"),// RadioButton einer Gruppe zuordnen und Beschriftung geben m_button("Gtk::Button"), m_table(3,2,true) // 3 Zeilen, 2 Spalten, homogene Aufteilung der Zellen { // Standardfenstergröße setzen; Breite: 400px Höhe: 170px set_default_size(400,170); // Titel setzen set_title("GTKmm Tutorial Teil 2"); // Widgets in das Table einfügen attach_widgets_to_table(); // Signale verbinden connect_signals(); // Tabelle dem Fenster übergeben add(m_table); // Alle Widgets anzeigen show_all_children(); } void MyWindow::attach_widgets_to_table() { // Button in Zelle(0,0) einfügen m_table.attach(m_button,0,1,0,1); // ToggleButton in Zelle(1,0) einfügen m_table.attach(m_toggle_button,1,2,0,1); // CheckButton in Zelle(0,1) einfügen m_table.attach(m_check_button1,0,1,1,2); // CheckButton in Zelle(1,1) einfügen m_table.attach(m_check_button2,1,2,1,2); // RadioButton in Zelle(0,2) einfügen m_table.attach(m_radio_button1,0,1,2,3); // RadioButton in Zelle(1,2) einfügen m_table.attach(m_radio_button2,1,2,2,3); } void MyWindow::connect_signals() { // Signale verbinden: m_button.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button_clicked)); m_toggle_button.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_toggle_button_clicked)); m_radio_button1.signal_clicked().connect(sigc::bind<int>(sigc::mem_fun(*this,&MyWindow::on_radio_button_clicked),1)); m_radio_button2.signal_clicked().connect(sigc::bind<int>(sigc::mem_fun(*this,&MyWindow::on_radio_button_clicked),2)); m_check_button1.signal_clicked().connect(sigc::bind<int,Gtk::CheckButton const *>(sigc::mem_fun(*this,&MyWindow::on_check_button_clicked),1,&m_check_button1)); m_check_button2.signal_clicked().connect(sigc::bind<int,Gtk::CheckButton const *>(sigc::mem_fun(*this,&MyWindow::on_check_button_clicked),2,&m_check_button2)); } void MyWindow::on_toggle_button_clicked() { Glib::ustring msg = "ToggleButton wurde angeklickt. Neuer Status ist: "; if(m_toggle_button.get_active()) msg += "gedrueckt"; else msg += "nicht gedrueckt"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_radio_button_clicked(int which) { Glib::ustring msg = "Der Status von RadioButton "; if(which == 1) msg += "1 hat sich geaendert"; else msg += "2 hat sich geaendert"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_check_button_clicked(int which, Gtk::CheckButton const * cb) { if(!cb) { Gtk::MessageDialog dia(*this,"Es ist ein Fehler aufgetreten",false,Gtk::MESSAGE_ERROR); dia.run(); return; } Glib::ustring msg = "CheckButton "; if(which == 1) msg += "1"; else msg += "2"; if(cb->get_active()) msg += " markiert"; else msg += " Markierung aufgehoben"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_button_clicked() { Gtk::MessageDialog dia(*this,"Button wurde angeklickt :)"); dia.run(); } int main(int argc, char **argv) { Gtk::Main main(argc,argv); MyWindow window; main.run(window); return 0; }
Und so sieht's aus:
4.Implementation eines weiteren Beispiels
#include <gtkmm.h> struct MyWindow : Gtk::Window { MyWindow(); ~MyWindow(); void on_button1_clicked(); void on_button2_clicked(); Gtk::Button m_button1; Gtk::Button m_button2; Gtk::Label m_label; Gtk::VBox m_vbox; Gtk::HBox m_hbox; }; MyWindow::MyWindow() : Gtk::Window(), m_button1("Klick mich1"), m_button2("Klick mich2"), m_label("<u><i><b>Ich bin ein Label</b></i></u>"), m_vbox(true,5), m_hbox(true,5) { set_title("GTKmm Tutorial Teil 2"); m_label.set_use_markup(true); m_hbox.pack_start(m_button1); m_hbox.pack_end(m_button2); m_button1.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button1_clicked)); m_button2.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button2_clicked)); m_vbox.pack_start(m_hbox); m_vbox.pack_start(m_label); add(m_vbox); set_default_size(200,100); show_all_children(true); } MyWindow::~MyWindow() { } void MyWindow::on_button1_clicked() { m_label.set_markup("<b><i>Button 1</i> wurde angeklickt</b>"); m_label.set_use_markup(true); } void MyWindow::on_button2_clicked() { m_label.set_markup("<b><u>Button 2</u> wurde angeklickt</b>"); m_label.set_use_markup(true); } int main(int argc, char **argv) { Gtk::Main main(argc,argv); MyWindow window; main.run(window); return 0; }
Und last but not least: So sieht's aus:
So, diesmal gab's viel Code und wenig Erklärungen, so wird es auch im nächsten Teil aussehen, da ich nicht glaube, dass es zu den Widgets viel zu erzählen gibt. Ich hoffe, dass dieses Tutorial hilfreich war.
Ich schmeiße mich gleich auch ans nächste Tutorial, damit da nicht wieder so ne Ewigkeit dazwischen liegt
BR
evilissimo
-
Am Anfang gabs meines Erachtens noch einige Fehler.
Vor allem zogen sich Kommatafehler durch den Artikel.Mr. B
-
Grafische Benutzerschnittstellen in C++ mit GTKmm betriebssystemunabhängig gestalten Teil 2
Nach einer langen Auszeit habe ich mich dazu aufgerafft, endlich am Tutorial weiterzuschreiben. Ich hoffe, ihr könnt mir diese Pause verzeihen.
Wie versprochen behandle ich in diesem Teil das GTK+ Boxensystem, Labels, Buttons und Signale (Events). Ich werde ein paar Anwendungsbeispiele zu dem jeweiligen Thema anführen. Zum Schluss werde ich ein Beispiel mit allen Widgets zeigen und die eventuell noch unbekannten Schritte zusätzlich kommentieren.
1.GTK+ Boxensystem
1.1 Einführung
In GTK+ und somit auch in GTKmm werden die Widgets mit so genannten „boxes“ angeordnet. Das sind Container, die die Widgets aufnehmen können. Mit diesem Konzept ordnet man Widgets nur relativ und nicht absolut an - im Gegensatz zu dem, wie man es von den bekannteren GUI-Designern kennt (z.b. Borland C++ Builder, Visual C++, Visual C# usw.)
Dieses Konzept ermöglicht ein dynamisches Anpassen der Größe der Widgets zur jeweiligen Fenstergröße, ohne das sich der Programmierer darum kümmern müsste.
Von diesen Containern gibt es verschiedene Arten.
Es gibt Container, die nur jeweils ein Widget aufnehmen können ( diese wurden von Gtk::Bin abgeleitet ), Container die mehrere Widgets aufnehmen können, aber nur in eine Richtung, sprich Horizontal oder Vertikal. Dies wären z.B. Gtk::Hbox für die horizontale und Gtk::VBox für die vertikale Richtung.
Und zu guter Letzt gibt es noch eine Tabelle ( Gtk::Table ), in der man n x m Widgets, je nach dem, wie man es braucht, unterbringen kann, eben in einer tabellarischen Anordnung.Eine Besonderheit ist aber das Widget Gtk::Fixed. Dies kann Elemente an festen Koordinaten aufnehmen, welche dann aber nicht resizable sind.
Ich werde auf diese Widgets nicht näher eingehen, da ich der Meinung bin, dass man auf diese auch verzichten kann, und mir das Programmieren mit dem Boxensystem wesentlich einfacher und eleganter erscheint.1.2 Widgets und Boxes
Die Container Gtk::HBox und Gtk::VBox haben neben dem Standard-Konstruktor einen Konstruktor mit folgenden Parametern:
Gtk::HBox( bool homogenous , int padding ); Gtk::VBox( bool homogenous , int padding );
homogenous gibt hier an, dass der Platz der Widgets gleichmäßig aufgeteilt wird, wenn man true übergibt. Der Parameter padding gibt an, wie viel Platz in Pixeln zwischen den Bereichen der Widgets sein soll.
Um Widgets in die Boxes einzufügen, haben diese folgende Methoden:
void pack_start(Gtk::Widget& child, PackOptions options = PACK_EXPAND_WIDGET, guint padding = 0); void pack_end(Gtk::Widget& child, PackOptions options = PACK_EXPAND_WIDGET, guint padding = 0);
Der erste Parameter ist eine Referenz auf das Widget, das wir hinzufügen wollen, z.B. ein Gtk::Button Objekt.
Der zweite Parameter gibt die PackOption an. Davon hängt es ab, wie sich die Anordnung der Widgets abspielt.
Es gibt 3 verschiedene Optionen:
- PACK_SHRINK
- PACK_EXPAND_WIDGET
- PACK_EXPAND_PADDING
Gibt man PACK_SHRINK an, wird nur so viel Platz verwendet, wie das Widget wirklich braucht, und es wird niemals expandiert.
Bei PACK_EXPAND_WIDGET wird der freie Platz ausgefüllt, indem das Widget einfach vergrößert wird.
Bei PACK_EXPAND_PADDING wird der freie Platz einfach durch Abstände ausgefüllt; das Widget ist dann so gesehen zentriert in seinem Bereich. Der Bereich ist abhängig von dem Drumherum und das wird alles immer dynamisch angepasst.
Der dritte Parameter padding gibt an, wie viel Platz das Widget drum herum haben soll. Im Gegensatz zu dem Parameter des Konstruktors ist hier der Platz um das Widget herum gemeint.
1.3 Widgets und Tables
Gtk::Table hat folgenden Konstruktor:
Gtk::Table::Table(guint n_rows = 1, guint n_columns = 1, bool homogeneous = false)
Im Grunde ist dieser eigentlich selbsterklärend. n_rows gibt an, wie viele Zeilen das Table haben soll, und n_columns, wie viele Spalten.
homogenous gibt an, ob die Zellen homogen, sprich immer gleich groß, sein sollen.
Generell wäre ich vorsichtig mit der Verwendung von homogenous = true, da es viele Widgets gibt, die sehr viel Platz brauchen und es damit eine negative Auswirkung auf alle Widgets haben kann. Aber natürlich kann dieser Effekt auch erwünscht sein.
Das Beste ist, dass man sich generell erst mal mit den ganzen Containern auseinander setzt und damit etwas rumspielt, bis man das alles verstanden hat.
Das ist einfacher, als ellenlange Texte darüber zu lesen. Die eigene Erfahrung ist immer noch die lehrreichste. (Das ist meine Meinung )So und nun möchte ich euch erst mal schocken:
void Gtk::Table::attach(Gtk::Widget& child, guint left_attach, guint right_attach, guint top_attach, guint bottom_attach, guint xoptions = Gtk::FILL | Gtk::EXPAND, guint yoptions = Gtk::FILL | Gtk::EXPAND, guint xpadding = 0, guint ypadding = 0);
Diese Methode ist natürlich auf den ersten Blick ziemlich abschreckend, aber die Verwendung ist einfacher, als es auf den ersten Blick aussehen mag.
child ist das Widget, das wir einfügen möchten und das als Referenz auf das Widget-Objekt übergeben wird.
left_attach gibt an, welche Zelle die Startposition auf der X-Achse ist.
right_attach gibt an, welche Zelle die Endposition auf der X-Achse ist, wobei die Endposition mindestens 1 größer als die Startposition sein muss.
top_attach gibt an, welche Zelle die Startposition auf der Y-Achse ist.
bottom_attach gibt an, welche Zelle die Endposition auf der Y-Achse ist, wobei die Endposition mindestens 1 größer als die Startposition sein muss.xoptions gibt an, wie sich das Widget innerhalb seiner Zelle auf der X-Achse verhalten soll. Dies ist den PackOptions bei den Boxes ähnlich.
yoptions gibt an, wie sich das Widget innerhalb seiner Zelle auf der Y-Achse verhalten soll. Dies ist den PackOptions bei den Boxes ähnlich.xpadding gibt an, wie viel Platz das Widget links und rechts von sich haben soll (freier Platz).
xpadding gibt an, wie viel Platz das Widget oben und unten von sich haben soll (freier Platz).Um das Ganze zu verstehen, schauen wir uns einfach mal den Aufbau einer Tabelle an:
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | d | e | f | +---+---+---+ 2 | g | h | i | +---+---+---+
Ich habe die Buchstaben in die Tabelle eingefügt, um besser erklären zu können, wie das Ganze dann beim Aufruf auszusehen hat. Die Zahlen entsprechen dem Zeilen- und Reihenindex.
Angenommen, wir wollen hier Widget 'a' in Zelle 0/0 einfügen, dann würde der Aufruf folgendermaßen aussehen:
m_table.attach( a , 0 , 1 , 0 , 1 );
für b sieht das Ganze so aus:
m_table.attach( b , 1 , 2 , 0 , 1 );
für e sieht das Ganze dann so aus:
m_table.attach( e , 1 , 2 , 1 , 2 );
für i sieht das Ganze dann so aus:
m_table.attach( i , 2 , 3 , 2 , 3 );
Nun möchte man aber, dass sich ein Widget über mehrere Zellen erstreckt. Wie das Widget 'd' in diesem Beispiel:
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | d | +---+---+---+ 2 | e | f | g | +---+---+---+
Dann würde das Einfügen für das Widget 'd' folgendermaßen aussehen:
m_table.attach( d , 0 , 3 , 1 , 2 );
Und für folgendes Layout
0 1 2 +---+---+---+ 0 | a | b | c | +---+---+---+ 1 | | + d + 2 | | +---+---+---+
so:
m_table.attach( d , 0 , 3 , 1 , 3 );
Bei den xoptions und yoptions gibt es folgende Optionen:
- FILL
- SHRINK
- EXPAND
Bei Gtk::FILL wird das Widget, sollte es kleiner als die Zelle selbst sein, auf Zellengröße vergrößert.
Gtk::EXPAND zwingt das Table zum Größerwerden, sollte der Platz für das Widget nicht ausreichen.
Wenn die Tabelle weniger Platz bekommt, als sie braucht, weil der Benutzer z.B. die Fenstergröße ändert, verschwindet das Wigdet einfach. Wenn man Gtk::SHRINK angibt, werden die Widgets in der Tabelle verkleinert, damit sie in den Bereich passen.1.4 Single Widget Container
Es gibt auch Container-Widgets, die nur ein einziges Widget aufnehmen können. Bestes Beispiel ist Gtk::Window.
Diese Widgets haben nur die Methode add zum Hinzufügen der Widgets und nehmen als Parameter eine Referenz auf das Widget, das hinzugefügt werden soll.
Es gibt auch noch Ausnahmen bei den "Single Widget Containern": Das ist Gtk::Paned, aber auf das werde ich ein anderes Mal zu sprechen kommen
1.5 Anwendung
In dem Beispiel werde ich bereits das erste Mal Gtk::Label benutzen, um zu verdeutlichen, wie man die Boxes verwendet.
struct MyWindow : Gtk::Window { MyWindow(); Gtk::VBox m_vbox; Gtk::HBox m_hbox; Gtk::Label m_label1; Gtk::Label m_label2; Gtk::Label m_label3; }; MyWindow::MyWindow() : Gtk::Window(), m_vbox(true,5),// homogene Ausrichtung und 5 px Abstand m_hbox(true,5),// homogene Ausrichtung und 5 px Abstand m_label1("Label 1"), m_label2("Label 2"), m_label3("Label 3") { // Text der Titelleiste setzen set_title("GTKmm Tutorial Teil 2"); // m_label1 der horizontalen box als erstes Element übergeben m_hbox.pack_start(m_label1); // m_label2 der horizontalen box als zweites Element übergeben m_hbox.pack_start(m_label2); // m_hbox der vertikalen box als erstes Element übergeben m_vbox.pack_start(m_hbox); // m_label3 der vertikalen box als zweites Element übergeben m_vbox.pack_start(m_label3); // die vertikale Box an das Fenster übergeben add(m_vbox); // sorgt dafür, dass alle Widgets angezeigt werden show_all_children(); }
Und so sieht's aus:
Ein Beispiel für die Verwendung eines Gtk::Table Containers findet ihr im Anwendungsbeispiel für die Buttons
2.Signale
2.1 Einführung
In GTKmm wird, um auf Signale/Events zu reagieren, die Bibliothek SigC++ verwendet. SigC++ ist sehr flexibel und mächtig und ist sehr vielseitig verwendbar. Es ist möglich, Funktionen und Methoden aller Art zu verwenden, und es besteht sogar die Möglichkeit, zusätzliche Parameter an ein Signal zu binden, um zusätzliche Daten an den eigenen Signalhandler zu übergeben.
Um nicht zu ausschweifend zu werden, zeige ich euch einfach ein paar Verwendungsbeispiele:
2.2 Anwendung
2.2.1 Eine Funktion als Signalhandler
void on_button_clicked(); // Unser Signalhandler Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur Veranschaulichung button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
Wie man sieht, ist die Verwendung recht unkompliziert. Die meisten Widgets haben spezielle Zugriffsmethoden, die es einem erlauben, Signalhandler zu setzen.
Gtk::Button hat in diesem Fall z.B. die Methode Gtk::Button::signal_clicked(), um den Handler zu setzen, der auf ein Klick-Ereignis reagiert.
Die Signale aller Widgets zu beschreiben, würde den Rahmen dieses Tutorials sprengen. Sie sollten daher der GTKmm-Dokumentation entnommen werden.
2.2.2 Methoden als Signalhandler
struct test { void on_button_clicked(); // Unser Signalhandler }; Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur Veranschaulichung test test_obj; button.signal_clicked().connect(sigc::mem_fun(test_obj,&test::on_button_clicked));
Um Methoden zu binden, bietet SigC++ die Funktion sigc::mem_fun.
Als ersten Parameter nimmt sigc::mem_fun eine Referenz auf das Objekt der Methode und als zweiten Parameter den Methodenzeiger auf die Methode an.
2.2.3 Binden von Parametern
Da man manchmal zusätzliche Parameter bei einem Ereignis braucht, um auf ein Signal zu reagieren, bietet SigC++ die Funktion sigc::bind an, um zusätzliche Parameter an den Signalhandler zu übergeben.
struct data {}; struct test { void on_button_clicked(data d); }; Gtk::Button button("Ich bin ein button"); // Wir verwenden einen Button zur Veranschaulichung test test_obj; data d; button.signal_clicked().connect( sigc::bind<data>( sigc::mem_fun( test_obj , &test::on_button_clicked ), d ) );
3.Buttons
3.1 Einführung
In GTKmm gibt es neben den "normalen" Buttons auch RadioButtons, CheckButtons (aka CheckBox) und so genannte ToggleButtons. Viel zu erzählen gibt es hier nicht.
Schaut euch einfach mal das Beispiel an und ihr seht, dass die Verwendung sehr simpel ist. In diesem Beispiel kann man auch schön die Verwendung der oben besprochenen Parameterbindung sehen. Des Weiteren verwende ich ein Gtk::Table als Container, um dessen Verwendung auch darzustellen.
3.2 Anwendung
struct MyWindow : Gtk::Window { MyWindow(); private: Gtk::ToggleButton m_toggle_button; Gtk::CheckButton m_check_button1; Gtk::CheckButton m_check_button2; Gtk::RadioButtonGroup m_radiogroup; Gtk::RadioButton m_radio_button1; Gtk::RadioButton m_radio_button2; Gtk::Button m_button; Gtk::Table m_table; private: void attach_widgets_to_table(); void connect_signals(); void on_toggle_button_clicked(); void on_radio_button_clicked(int); void on_check_button_clicked(int,Gtk::CheckButton const *); void on_button_clicked(); }; MyWindow::MyWindow() : Gtk::Window(), m_toggle_button("Gtk::ToggleButton"), m_check_button1("Gtk::CheckButton 1"), m_check_button2("Gtk::CheckButton 2"), m_radiogroup(), m_radio_button1(m_radiogroup,"Gtk::RadioButton 1"),// RadioButton einer Gruppe zuordnen und Beschriftung geben m_radio_button2(m_radiogroup,"Gtk::RadioButton 2"),// RadioButton einer Gruppe zuordnen und Beschriftung geben m_button("Gtk::Button"), m_table(3,2,true) // 3 Zeilen, 2 Spalten, homogene Aufteilung der Zellen { // Standardfenstergröße setzen; Breite: 400px Höhe: 170px set_default_size(400,170); // Titel setzen set_title("GTKmm Tutorial Teil 2"); // Widgets in das Table einfügen attach_widgets_to_table(); // Signale verbinden connect_signals(); // Tabelle dem Fenster übergeben add(m_table); // Alle Widgets anzeigen show_all_children(); } void MyWindow::attach_widgets_to_table() { // Button in Zelle(0,0) einfügen m_table.attach(m_button,0,1,0,1); // ToggleButton in Zelle(1,0) einfügen m_table.attach(m_toggle_button,1,2,0,1); // CheckButton in Zelle(0,1) einfügen m_table.attach(m_check_button1,0,1,1,2); // CheckButton in Zelle(1,1) einfügen m_table.attach(m_check_button2,1,2,1,2); // RadioButton in Zelle(0,2) einfügen m_table.attach(m_radio_button1,0,1,2,3); // RadioButton in Zelle(1,2) einfügen m_table.attach(m_radio_button2,1,2,2,3); } void MyWindow::connect_signals() { // Signale verbinden: m_button.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button_clicked)); m_toggle_button.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_toggle_button_clicked)); m_radio_button1.signal_clicked().connect(sigc::bind<int>(sigc::mem_fun(*this,&MyWindow::on_radio_button_clicked),1)); m_radio_button2.signal_clicked().connect(sigc::bind<int>(sigc::mem_fun(*this,&MyWindow::on_radio_button_clicked),2)); m_check_button1.signal_clicked().connect(sigc::bind<int,Gtk::CheckButton const *>(sigc::mem_fun(*this,&MyWindow::on_check_button_clicked),1,&m_check_button1)); m_check_button2.signal_clicked().connect(sigc::bind<int,Gtk::CheckButton const *>(sigc::mem_fun(*this,&MyWindow::on_check_button_clicked),2,&m_check_button2)); } void MyWindow::on_toggle_button_clicked() { Glib::ustring msg = "ToggleButton wurde angeklickt. Neuer Status ist: "; if(m_toggle_button.get_active()) msg += "gedrueckt"; else msg += "nicht gedrueckt"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_radio_button_clicked(int which) { Glib::ustring msg = "Der Status von RadioButton "; if(which == 1) msg += "1 hat sich geaendert"; else msg += "2 hat sich geaendert"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_check_button_clicked(int which, Gtk::CheckButton const * cb) { if(!cb) { Gtk::MessageDialog dia(*this,"Es ist ein Fehler aufgetreten",false,Gtk::MESSAGE_ERROR); dia.run(); return; } Glib::ustring msg = "CheckButton "; if(which == 1) msg += "1"; else msg += "2"; if(cb->get_active()) msg += " markiert"; else msg += " Markierung aufgehoben"; Gtk::MessageDialog dia(*this,msg); dia.run(); } void MyWindow::on_button_clicked() { Gtk::MessageDialog dia(*this,"Button wurde angeklickt :)"); dia.run(); } int main(int argc, char **argv) { Gtk::Main main(argc,argv); MyWindow window; main.run(window); return 0; }
Und so sieht's aus:
4.Implementation eines weiteren Beispiels
#include <gtkmm.h> struct MyWindow : Gtk::Window { MyWindow(); ~MyWindow(); void on_button1_clicked(); void on_button2_clicked(); Gtk::Button m_button1; Gtk::Button m_button2; Gtk::Label m_label; Gtk::VBox m_vbox; Gtk::HBox m_hbox; }; MyWindow::MyWindow() : Gtk::Window(), m_button1("Klick mich1"), m_button2("Klick mich2"), m_label("<u><i><b>Ich bin ein Label</b></i></u>"), m_vbox(true,5), m_hbox(true,5) { set_title("GTKmm Tutorial Teil 2"); m_label.set_use_markup(true); m_hbox.pack_start(m_button1); m_hbox.pack_end(m_button2); m_button1.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button1_clicked)); m_button2.signal_clicked().connect(sigc::mem_fun(*this,&MyWindow::on_button2_clicked)); m_vbox.pack_start(m_hbox); m_vbox.pack_start(m_label); add(m_vbox); set_default_size(200,100); show_all_children(true); } MyWindow::~MyWindow() { } void MyWindow::on_button1_clicked() { m_label.set_markup("<b><i>Button 1</i> wurde angeklickt</b>"); m_label.set_use_markup(true); } void MyWindow::on_button2_clicked() { m_label.set_markup("<b><u>Button 2</u> wurde angeklickt</b>"); m_label.set_use_markup(true); } int main(int argc, char **argv) { Gtk::Main main(argc,argv); MyWindow window; main.run(window); return 0; }
Und last but not least: So sieht's aus:
So, diesmal gab's viel Code und wenig Erklärungen, so wird es auch im nächsten Teil aussehen, da ich nicht glaube, dass es zu den Widgets viel zu erzählen gibt. Ich hoffe, dass dieses Tutorial hilfreich war.
Ich schmeiße mich gleich auch ans nächste Tutorial, damit da nicht wieder so ne Ewigkeit dazwischen liegt
BR
evilissimo
-
Ich will ja nicht nerven, aber im Abschnitt 1.2 fehlt bei der Beschreibung zu "PACK_EXPAND_PADDING" ein Leerzeichen zwischen "Abstände" und "ausgefüllt". :p
@evilissimo: Kannst du ja grad noch editieren@Mr. B: , ich hatte wohl einiges übersehen...
-
-predator- schrieb:
Ich will ja nicht nerven, aber im Abschnitt 1.2 fehlt bei der Beschreibung zu "PACK_EXPAND_PADDING" ein Leerzeichen zwischen "Abstände" und "ausgefüllt". :p
@evilissimo: Kannst du ja grad noch editierendone.
-
Ich mecker ja ungern so spät... aber könntest du den Code so umbrechen, dass man den Artikel nicht ständig hin und her scrollen muss, um ihn zu lesen?
Wäre schön, wenn die Breite in eine 1024er Auflösung passt.Und ich hab mal den Betreff vom E-Artikel so angepasst, dass er wie der vom ersten Teil aussieht.