[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 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 muss

    xoptions 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 Boxes

    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( 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 muss

    xoptions 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 Boxes

    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 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 editieren

    done.



  • 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.


Anmelden zum Antworten