GTKmm Tutorial Teil 3



  • Grafische Benutzerschnittstellen in C++ mit GTKmm betriebssystemunabhängig gestalten Teil 3

    In diesem Teil werde ich Gtk::Entry, Gtk::SpinButton, Gtk::Statusbar und Gtk::ProgressBar vorstellen. Zu den Widgets gibts meistens nicht viel zu erzählen und der Code erklärt mehr als langes Gerede.
    Schaut euch die Beispiele an und ihr werdet verstehen, wie das ganze funktioniert (hoffe ich mal :p). So und nun geht es auch schon los.

    1 Gtk::Entry

    Das Gtk::Entry Widget kennen die meisten eher unter Edit. Hierbei handelt es sich um ein einfaches, einzeiliges Eingabefeld.

    Ein Verwendungsbeispiel:

    #include <gtkmm.h>
    
    struct EntryTutorial : Gtk::Window
    {
        EntryTutorial();
    private:
        Gtk::Entry  m_entry;
        Gtk::Button m_button;
        Gtk::HBox   m_hbox;
    
        void on_entry_changed();
    };
    
    EntryTutorial::EntryTutorial()
    : Gtk::Window(), 
      m_entry(),
      m_button(Gtk::Stock::QUIT),
      m_hbox(true,10)
    {
        // Gtk::Entry als Erstes 
        m_hbox.pack_start(m_entry);
    
        // Button als Zweites 
        m_hbox.pack_start(m_button);
    
        // HBox dem Fenster übergeben
        add(m_hbox);
    
        // Hier verbinden wir das clicked signal des Buttons mit der hide Methode 
        // der Gtk::Window Klasse. Damit kann man dann die Anwendung beenden
        m_button.signal_clicked().connect(sigc::mem_fun(*this,&Gtk::Window::hide));
    
        // Das Signal wird nach jedem Zeichen aufgerufen, das entweder eingegeben oder gelöscht wurde!
        // Damit kann man super Eingaben überprüfen und evtl. bestimmte Zeichen blocken :)
        m_entry.signal_changed().connect(sigc::mem_fun(*this,&EntryTutorial::on_entry_changed));
    
        show_all_children();
    }
    
    void EntryTutorial::on_entry_changed()
    {
        // Da ich hier ein Umlaut verwenden möchte und der String UTF-8 ist, 
        // kodiere ich das 'ä' hier von Hand als 0xC3A4
        Glib::ustring msg = "Der Text des Entries hat sich ge\xC3\xA4ndert. Neuer Text: ";
    
        // Den Text, der im Entry eingegeben wurde, an unsere Nachricht anhängen
        msg += m_entry.get_text();
    
        Gtk::MessageDialog dia(*this,msg);
        dia.run();
    }
    
    int main(int argc, char **argv)
    {
        Gtk::Main main(argc,argv);
        EntryTutorial window;
        main.run(window);
        return 0;
    }
    

    2 Gtk::SpinButton

    Bei einem Gtk::SpinButton handelt es sich um ein Eingabefeld für numerische Werte mit einem Up/Down Widget (siehe Screenshot unterhalb des Codes ;)).

    #include <gtkmm.h>
    #include <boost/format.hpp>
    
    struct SpinButtonTutorial : Gtk::Window
    {
        SpinButtonTutorial();
    private:
        Gtk::Adjustment m_spin_button_adjustment;
        Gtk::SpinButton m_spin_button;
        Gtk::Button     m_button;
        Gtk::HBox       m_hbox;
        int             m_previous_value;
        void on_spinbutton_value_changed();
    };
    
    SpinButtonTutorial::SpinButtonTutorial()
    : Gtk::Window(), 
      m_spin_button_adjustment(6,1,12,1,6,0),// value = 6, lower = 1, upper = 12, step_inc = 1, page_inc = 6, pagesize = 0
      m_spin_button(m_spin_button_adjustment),// SpinButton mit den Randbedingungen initialisieren
      m_button(Gtk::Stock::QUIT), // Stockbutton wie gehabt ;)
      m_hbox(false,10),
      m_previous_value(0)
    {
        set_title("GTKmm Tutorial Teil 3");
    
        // Gtk::SpinButton als Erstes 
        m_hbox.pack_start(m_spin_button,Gtk::PACK_SHRINK);
    
        m_previous_value = m_spin_button.get_value_as_int();
    
        // Button als Zweites 
        m_hbox.pack_start(m_button);
    
        // 10 px Rand um die Horizontale Box
        m_hbox.set_border_width(10);
    
        // HBox dem Fenster übergeben
        add(m_hbox);
    
        // Hier verbinden wir das clicked signal des Buttons mit der hide Methode 
        // der Gtk::Window Klasse. Damit kann man dann die Anwendung beenden
        m_button.signal_clicked().connect(sigc::mem_fun(*this,&Gtk::Window::hide));
    
        m_spin_button.signal_value_changed().connect(sigc::mem_fun(*this,&SpinButtonTutorial::on_spinbutton_value_changed));
    
        show_all_children();
    }
    
    void SpinButtonTutorial::on_spinbutton_value_changed()
    {
        // Ich benutze hier Boost.Format, um meinen String zu erzeugen.
        // Da Glib::ustring einen Pperator std::string() hat und man somit 
        // in der Lage ist, das Glib::ustring objekt wie ein std::string zu benutzen
        Glib::ustring msg = "Wert hat sich von %1% zu %2% ge\xC3\xA4ndert."; 
    
        boost::format fmt(msg);
    
        // Den alten und den aktuellen Wert an das boost::format Objekt übergeben,
        // damit der String erzeugt werden kann
        fmt % m_previous_value % m_spin_button.get_value_as_int();
    
        // Nun speichere ich den neuen Wert, damit wir den das nächste Mal zur 
        // Verfügung haben
        m_previous_value = m_spin_button.get_value_as_int();
    
        // Message Dialog wie gehabt ;)
        Gtk::MessageDialog dia(*this,fmt.str());
        dia.run();
    }
    
    int main(int argc, char **argv)
    {
        Gtk::Main main(argc,argv);
        SpinButtonTutorial window;
        main.run(window);
        return 0;
    }
    

    3 Gtk::ProgressBar

    Eine ProgressBar kann man vertikal oder horizontal verwenden. Ich habe beides in diesem Code untergebracht.

    Des Weiteren gibt es zwei Möglichkeiten, eine ProgressBar zu benutzen. Entweder man benutzt sie, um den Fortschritt anzuzeigen, um damit die aktuelle Prozentzahl des Fortschritts anzuzeigen, oder man benutzt sie mit pulse(), um nur anzuzeigen, dass es aktiv ist.

    #include <gtkmm.h>
    #include <boost/format.hpp>
    
    struct ProgressBarTutorial : Gtk::Window
    {
        ProgressBarTutorial();
    private:
    
        Gtk::VBox        m_vbox1;
        Gtk::VBox        m_vbox2;
        Gtk::HBox        m_hbox;
    
        Gtk::ProgressBar m_progressbar_h;
        Gtk::ProgressBar m_progressbar_v;
    
        Gtk::Button      m_button;
    
        bool on_activity_step();
        bool on_progress_step();
    };
    
    ProgressBarTutorial::ProgressBarTutorial()
    : Gtk::Window(),
      m_vbox1(),
      m_vbox2(),
      m_hbox(),
      m_progressbar_h(),
      m_progressbar_v(),
      m_button(Gtk::Stock::QUIT)
    {
        set_title("GTKmm Tutorial Teil 3");
    
        // Wir möchten eine vertikale ProgressBar
        m_progressbar_v.set_orientation(Gtk::PROGRESS_BOTTOM_TO_TOP);
    
        // Aktuellen Fortschritt der horizontalen ProgressBar auf 0 setzen
        m_progressbar_h.set_fraction(0);
    
        // Ab hier die übliche Anordnung der Widgets
        m_hbox.pack_start(m_progressbar_v,Gtk::PACK_SHRINK);
        m_hbox.set_spacing(10);
        m_vbox2.pack_start(m_progressbar_h,Gtk::PACK_EXPAND_PADDING);
    
        m_hbox.pack_start(m_vbox2);
    
        m_vbox1.pack_start(m_hbox);
        m_vbox1.pack_start(m_button,Gtk::PACK_SHRINK);
    
        m_vbox1.set_spacing(10);
        m_vbox1.set_border_width(10);
    
        add(m_vbox1);
    
        // Signale verbinden    
        // In diesem Falle verwenden wir hier eine Möglichkeit, um Threads zu vermeiden
        // Normalerweise würde man für dieses Beispiel wohl zwei Threads starten 
        // Wir verwenden einfach eine andere Möglichkeit
        // Hierfür bietet die glib das Signal "timeout" 
        // Das nimmt mehrere SignalHandler auf und löst diese dann nach n angegebenen Millisekunden aus
    Glib::signal_timeout().connect(sigc::mem_fun(*this,&ProgressBarTutorial::on_activity_step),500);
        Glib::signal_timeout().connect(sigc::mem_fun(*this,&ProgressBarTutorial::on_progress_step),1000);    
    
        // Signal verbinden, um das Fenster zu schließen(=> Beenden)
        m_button.signal_clicked().connect(sigc::mem_fun(*this,&Gtk::Window::hide));
    
        // Alle Widgets anzeigen
        show_all_children();
    }
    
    bool ProgressBarTutorial::on_activity_step()
    {
        // Nächsten Pulse-Schritt ausführen
        m_progressbar_v.pulse();
    
        // Wir geben true zurück, weil wir möchten, dass dieses timeout signal 
        // wieder aufgerufen wird
        return true;
    }
    
    bool ProgressBarTutorial::on_progress_step()
    {
        // Wir holen uns die aktuelle Prozentzahl und addieren 0.1 (als Schritt)
        // hinzu
        double cur = m_progressbar_h.get_fraction() + 0.1;
        // Prüfen, ob 1.0 oder mehr erreicht wurden (wenn ja: zurücksetzen auf 0)
        if(cur >= 1.0)
            cur = 0;
        // Neuen Fortschrittstand setzen
        m_progressbar_h.set_fraction(cur);
        // Prozentanzeige (Text im Hintergrund der ProgressBar) setzen
        boost::format fmt("%i %%");
        fmt % (cur * 100);
        m_progressbar_h.set_text(fmt.str());
    
        // Wir geben true zurück ,weil wir möchten, dass dieses timeout signal 
        // wieder aufgerufen wird
        return true;
    }
    
    int main(int argc, char **argv)
    {
        Gtk::Main main(argc,argv);
        ProgressBarTutorial window;
        main.run(window);
        return 0;
    }
    

    4 Gtk::StatusBar

    #include <gtkmm.h>
    #include <boost/format.hpp>
    
    struct StatusBarTutorial : Gtk::Window
    {
        StatusBarTutorial();
        ~StatusBarTutorial();
    private:
        void on_button_push_clicked();
        void on_button_pop_clicked();
    private:
        Gtk::VBox m_vbox;
        Gtk::HBox m_hbox;
        Gtk::Button m_button_push;
        Gtk::Button m_button_pop;
        Gtk::Statusbar m_statusbar;
        unsigned m_context_id;
    };
    
    StatusBarTutorial::StatusBarTutorial()
    : m_vbox(false,12),
      m_button_push("Push"), 
      m_button_pop("Pop"),
      m_statusbar(),
      m_context_id(0)
    {
        m_hbox.pack_start(m_button_push);
    
        m_hbox.pack_start(m_button_pop);
    
        m_vbox.pack_start(m_hbox,Gtk::PACK_EXPAND_PADDING);
    
        // Ich würde, immer wenn ihr eine Statusbar verwendet,
        // diese in eine VBox packen und dann mit Gtk::PACK_SHRINK
        // in das VBox Objekt packen, damit die Statusbar auch wie eine 
        // Statusbar aussieht ^^ 
        // Sonst ist es etwas sehr verschoben ;)
        m_vbox.pack_start(m_statusbar,Gtk::PACK_SHRINK);
    
        m_button_push.signal_clicked()
            .connect(sigc::mem_fun(*this,&StatusBarTutorial::on_button_push_clicked));
    
        m_button_pop.signal_clicked()
            .connect(sigc::mem_fun(*this,&StatusBarTutorial::on_button_pop_clicked));
    
        add(m_vbox);
    
        show_all_children();
    }
    
    StatusBarTutorial::~StatusBarTutorial()
    {   
    }
    
    void StatusBarTutorial::on_button_push_clicked()
    {   
        static unsigned count = 0;
        boost::format fmt("Text Nr %i");
        fmt % count++;
        // Text auf der statusbar stack setzen
        m_statusbar.push(fmt.str());
    }
    
    void StatusBarTutorial::on_button_pop_clicked()
    {
        // Obersten Text von der Statusbar stack entfernen
        m_statusbar.pop();
    }
    
    int main(int argc, char ** argv)
    {
        Gtk::Main main(argc,argv);
        StatusBarTutorial window;
        main.run(window);
        return EXIT_SUCCESS;
    }
    

    Dieses Mal war es extrem viel Code mit Kommentaren. Ich hoffe, dass es trotzdem verständlich genug war, denn ich denke, dass man mehr vom Praktischen lernt als vom theoretischen Gefasel 😉

    Solltet ihr Fragen haben, einfach raus damit.

    So, bis zum nächsten Teil 🙂

    Vinzenz 'evilissimo' Feenstra

    Weitere Teile:
    Teil 1 des Tutorials
    Teil 2 des Tutorials



  • Hi, hat wieder Spaß gemacht deinen Code zu lesen 😃

    Ein Frage:

    Wieso konvertierst du nicht die String die Umlaute haben in UTF-8 Strings, anstatt diese komischen Zeichen hinzuschreiben ???

    Hat das irgendwelche Nachteile ?

    Glib::ustring msg = Glib::locale_to_utf8("Der Text des Entries hat sich geändert. Neuer Text: ");
    


  • Man sagte mir in der GTKmm Mailinglist das man keine string literale nehmen soll.

    Ich hatte nämlich diverse crashes mit Glib::locale_to_utf8 also hab ich mich einfach dazu entschlossen die zu konvertieren und die Strings einfach so zu verwenden, da diese auf diese weise bereits UTF-8 sind 😉

    BR
    Vinzenz



  • Achso.
    Und wie kommst du auf diese Codes, stehen die Irgendwo ?? 😃



  • ja z.b. hier http://www.utf8-chartable.de/

    BR
    Vinzenz



  • Danke 😉



  • Mal ne dumme Frage, warum verwendest du immer eine eigene Struktur für dein Window? Nötig wäre das ja nicht, oder? OK, GTKmm legt es wohl auf OOP an, aber prozedural könnte ich das doch auch machen, oder?

    Und verstehe ich das richtig, die mit signal_changed() verbundene Funktion wird erst aufgerufen, nachdem Enter gedrückt wurde?



  • Hi,

    ja du könntest es rein theoretisch auch prozedual machen. Erstens finde ich es so besser wie es ist und zweitens ist das auch der sogesehen zu bevorzugende Weg mit GTKmm zu arbeiten.
    Nicht nur GTKmm auch GTK+ setzt auf Objektorientierung. Bei GTK+ eben nur mit den Mitteln die in C auch zur Verfügung stehen.

    Ich weiß jetzt nicht von welchem Beispiel du redest, aber ich gehe davon aus das du vom Entry-Widget Beispiel sprichst. Nein, das Signal kommt nach jedem eingegeben Zeichen. Sprich du gibst das Zeichen 'a' ein und das Signal wird ausgelöst, du gibst '5' ein und das Signal wird wieder ausgelöst, du löscht '5' und das Signal wird wiederum ausgelöst, etc. pp.

    BR
    Vinzenz



  • Danke, dann wäre ja alles klar! 🙂



  • Hi evilissimo, ich verfolge deine Artikel mit großem Interesse, deshalb kann ich mir auch eine Frage nicht verkneifen: Ist ein 4. Teil geplant / in Arbeit ?



  • Ja, weitere Teile sind in Arbeit, aber noch nicht fertiggestellt.

    Grüße

    GPC



  • Wie siehts aus bei dir? Kommst du weiter?



  • hil evilissimo,
    super Tutorial 🙂
    Alle drei Teile bieten einen guten Einstieg in die Materie.
    Hier ist noch ein Link auf eine eclipse Integration.
    http://kapo-cpp.blogspot.com/2007/02/gtkmm-and-eclipse.html


Log in to reply