GTKmm Tutorial Teil 5



  • Grafische Benutzerschnittstellen in C++ mit gtkmm betriebssystemunabhängig gestalten Teil 5

    In diesem Tutorial wird es um drei Komponenten gehen: Listen, Bäume und ComboBox-Felder. Auf den ersten Blick mögen sie nicht viel gemein haben, allerdings täuscht dieser hier, da sie alle auf der gleichen Technik basieren, die ich hier vorstellen möchte.

    1 Das Gtk::TreeView

    Das Gtk::TreeView wird zum Anzeigen von Listen und Baumstrukturen benutzt und ist eines der wichtigsten Widgets in einem Toolkit.

    Ähnlich dem Verhältnis von Gtk::TextView und Gtk::TextBuffer gibt es auch hier mehrere Widgets, die involviert sind.

    Eine gute Übersicht findet man unter [2].

    Während das Gtk::TreeView nur die Darstellung von Informationen übernimmt, werden die Daten in einem Gtk::TreeModel abgelegt. Das Gtk::TreeModel-Widget stellt nur eine generische Schnittstelle dar und wird dann je nach Bedarf durch eine abgeleitete Klasse wie Gtk::ListStore oder Gtk::TreeStore ersetzt. Selbstverständlich kann man auch sein eigenes TreeModel erstellen, indem man von Gtk::TreeModel ableitet. In den allermeisten Fällen wird dies jedoch nicht nötig sein.

    Außerdem wird ein Gtk::TreeView immer in ein Gtk::ScrolledWindow eingebettet, welches evilissimo in Teil 4 vorgestellt hat.

    Grundsätzlich gilt es folgende Dinge zu tun, um das Gtk::TreeView zu benutzen:

    • Das Modell für die Spalten definieren
    • Gtk::TreeView erstellen
    • Gtk::TreeModel (bzw. eine seiner Subklassen) erstellen und an Gtk::TreeView zuweisen
    • Daten in das Gtk::TreeModel reinladen

    1.1 Eine simple Liste

    In diesem simplen Beispiel werden wir ein paar Daten in einer Liste darstellen:

    #include <gtkmm.h>
    
    class Application : public Gtk::Window {
    	//Wir definieren hier unsere Spalten bzw. wie eine Zeile aussieht
    	class Columns : public Gtk::TreeModel::ColumnRecord {
    	public:
    		Columns() {
    			add(col_id);
    			add(col_name);
    			add(col_price);
    			add(col_stock);
            } 
    
    		~Columns() {}
    
    		Gtk::TreeModelColumn<unsigned> col_id;
    		Gtk::TreeModelColumn<Glib::ustring> col_name;
    		Gtk::TreeModelColumn<float> col_price;
    		Gtk::TreeModelColumn<bool> col_stock;
        };
    
    	Gtk::VBox topBox;
    
    	//Hier die drei wichtigsten Komponenten
    	Gtk::ScrolledWindow scrollWindow;
    	Gtk::TreeView treeView;
    	Glib::RefPtr<Gtk::ListStore> listStore;
    
    	Columns cols;
    
    	public:
    	Application() {
    		set_title("ListStore Example");
    		set_default_size(350,200);
    
    		//Wir erstellen das ListStore und weisen es dem TreeView zu
    		treeView.set_model(listStore = Gtk::ListStore::create(cols));
    
    		//Wir fuegen noch ein paar Daten ein
    		Gtk::TreeModel::Row row = *(listStore->append());
    		row[cols.col_id] = 2485;
    		row[cols.col_name] = "Staubsauger";
    		row[cols.col_price] = 79.99f;
    		row[cols.col_stock] = true;
    
    		row = *(listStore->append());
    		row[cols.col_id] = 2751;
    		row[cols.col_name] = "Mixer";
    		row[cols.col_price] = 25.75f;
    		row[cols.col_stock] = true;
    
    		row = *(listStore->append());
    		row[cols.col_id] = 2982;
    		row[cols.col_name] = "Waschmaschine";
    		row[cols.col_price] = 699.99f;
    		row[cols.col_stock] = false;
    
    		//Nun muessen wir noch angeben, welche Spalten angezeigt werden sollen
    		treeView.append_column("Produktnr.", cols.col_id);
    		treeView.append_column("Name", cols.col_name);
    
    		//Hier legen wir fest, wie die Spalte "Preis" formatiert werden soll
    		treeView.append_column_numeric("Preis (EUR)", cols.col_price, "%.2f");
    
    		//Die CheckBoxen werden automatisch gesetzt
    		treeView.append_column("Am Lager", cols.col_stock);
    
    		//Wir weisen das TreeView dem ScrollWindow zu
    		scrollWindow.add(treeView);
    		scrollWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
    
    		topBox.pack_start(scrollWindow);
    		add(topBox);
    
    		show_all_children();
        }
    	~Application() { }
    };
    
    int main(int argc, char **argv) {
    	Gtk::Main m(argc, argv);
    
    	Application app;
    	m.run(app);
    
    	return EXIT_SUCCESS;
    }
    

    So sieht das dann aus:

    Erwähnenswert ist, dass natürlich nur die Spalten angezeigt werden, die man per Gtk::TreeView::append_column-Methode hinzugefügt hat. Auf diese Art und Weise kann man leicht Daten "verstecken", indem man im ColumnModel weitere Eigenschaften deklariert, diese Spalten aber nicht hinzufügt. Später kann man die Spalte bei Bedarf mit einem Aufruf von Gtk::TreeView::append_column zur Laufzeit hinzufügen und mit Gtk::TreeView::remove_column auch wieder entfernen, wenn sie doch überflüssig wird.

    1.2 Eine Baumstruktur

    Nun ist so eine Liste mit Produkten ja etwas schönes, aber noch schöner wäre es, wenn wir unsere Produkte in Produktkategorien ablegen könnten. Hierzu werden wir nun eine Baumstruktur erstellen (der vorherige Quellcode ist Grundlage hierfür):

    #include <gtkmm.h>
    
    class Application : public Gtk::Window {
    	//Wir definieren hier unsere Spalten bzw. wie eine Zeile aussieht
    	class Columns : public Gtk::TreeModel::ColumnRecord {
    	public:
    		Columns() {
    			add(col_id);
    			add(col_name);
    			add(col_price);
    			add(col_stock);
            } 
    
    		~Columns() {}
    
    		Gtk::TreeModelColumn<unsigned> col_id;
    		Gtk::TreeModelColumn<Glib::ustring> col_name;
    		Gtk::TreeModelColumn<float> col_price;
    		Gtk::TreeModelColumn<bool> col_stock;
        };
    
    	Gtk::VBox topBox;
    	//Hier die drei wichtigsten Komponenten
    	Gtk::ScrolledWindow scrollWindow;
    	Gtk::TreeView treeView;
    
    	//Nun brauchen wir ein TreeStore anstelle eines ListStores
    	Glib::RefPtr<Gtk::TreeStore> treeStore;
    
    	Columns cols;
    
    	public:
    	Application() {
    		set_title("TreeStore Example");
    		set_default_size(350,250);
    
    		//Wir erstellen das TreeStore und weisen es dem TreeView zu
    		treeView.set_model(treeStore = Gtk::TreeStore::create(cols));
    
    		//Ein Knoten auf der ersten Ebene
    		Gtk::TreeModel::Row row = *(treeStore->append());
    		row[cols.col_id] = 2000;
    		row[cols.col_name] = "Elektrogeräte";
    		row[cols.col_price] = 0.0f;
    		row[cols.col_stock] = false;
    
    		//Drei Subknoten
    		Gtk::TreeModel::Row childrow = *(treeStore->append(row.children()));
    		childrow[cols.col_id] = 2485;
    		childrow[cols.col_name] = "Staubsauger";
    		childrow[cols.col_price] = 79.99f;
    		childrow[cols.col_stock] = true;
    
    		childrow = *(treeStore->append(row.children()));
    		childrow[cols.col_id] = 2751;
    		childrow[cols.col_name] = "Mixer";
    		childrow[cols.col_price] = 25.75f;
    		childrow[cols.col_stock] = true;
    
    		childrow = *(treeStore->append(row.children()));
    		childrow[cols.col_id] = 2982;
    		childrow[cols.col_name] = "Waschmaschine";
    		childrow[cols.col_price] = 699.99f;
    		childrow[cols.col_stock] = false;
    
    		//Wieder ein Knoten auf Ebene 1
    		row = *(treeStore->append());
    		row[cols.col_id] = 3000;
    		row[cols.col_name] = "Reinigungsmittel";
    		row[cols.col_price] = 0.0f;
    		row[cols.col_stock] = false;
    
    		//Und drei Subknoten
    		childrow = *(treeStore->append(row.children()));
    		childrow[cols.col_id] = 3149;
    		childrow[cols.col_name] = "Brof";
    		childrow[cols.col_price] = 5.99f;
    		childrow[cols.col_stock] = false;
    
    		childrow = *(treeStore->append(row.children()));
    		childrow[cols.col_id] = 3587;
    		childrow[cols.col_name] = "Kill it!";
    		childrow[cols.col_price] = 8.99f;
    		childrow[cols.col_stock] = true;
    
    		childrow = *(treeStore->append(row.children()));
    		childrow[cols.col_id] = 2982;
    		childrow[cols.col_name] = "Uber-Cleaner";
    		childrow[cols.col_price] = 15.25f;
    		childrow[cols.col_stock] = true;
    
    		//Nun moechten wir noch, dass man bestimmte Spalten editieren kann
    		//In dem Fall bleibt nur die Produktnummer unveraenderlich, alles 
    		//andere kann veraendert werden		
    		treeView.append_column("Produktnr.", cols.col_id);
    		treeView.append_column_editable("Name", cols.col_name);
    
    		//Hier legen wir fest, wie die Spalte "Preis" formatiert werden soll
    		treeView.append_column_numeric_editable("Preis (€)", cols.col_price,
    												"%.2f");
    
    		//Die CheckBoxen werden automatisch gesetzt
    		treeView.append_column_editable("Am Lager", cols.col_stock);
    
    		//Wir weisen das TreeView dem ScrollWindow zu
    		scrollWindow.add(treeView);
    		scrollWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
    
    		topBox.pack_start(scrollWindow);
    		add(topBox);
    
    		show_all_children();
        }
    	~Application() { }
    };
    
    int main(int argc, char **argv) {
    	Gtk::Main m(argc, argv);
    
    	Application app;
    	m.run(app);
    
    	return EXIT_SUCCESS;
    }
    

    Sieht doch schon ganz gut aus:

    Hier ein Bild, bei dem ein Zweig geschlossen ist:

    1.3 Auswahl und Zugriff auf Datensätze

    In der Regel wird man mit den Daten, die ein Gtk::TreeView beinhaltet, etwas anstellen müssen. Meistens wird der User eine oder mehrere Zeilen auswählen und auf diese soll nun eine Aktion ausgeführt werden.

    Generell gilt, dass auch das Gtk::TreeView mit Iteratoren arbeitet, um auf die Daten zuzugreifen. Hier heißt der Iterator Gtk::TreeModel::Iterator (dies ist ein typedef, im Original einfach Gtk::TreeIter).

    Nun, um herauszufinden, was gerade ausgewählt ist, erstellt man zuerst ein Gtk::TreeView::Selection-Objekt:

    Glib::RefPtr<Gtk::TreeView::Selection> selection = treeView.get_selection();
    

    Mit diesem Objekt können wir auch per set_mode(SelectionMode m) festlegen, ob Einfach- oder Mehrfachselektion verfügbar sein soll (Einfachauswahl ist die Voreinstellung). Abgesehen davon verwaltet dieses Objekt alles, was irgendwie mit der Auswahl zusammenhängt. So kann man per select(Gtk::TreeModel::Iterator) auch eine Auswahl setzen.

    Nun, abhängig von Einfach- bzw. Mehrfachauswahl entscheiden wir über das weitere Vorgehen. Bei Einfachauswahl hat man es recht einfach:

    Gtk::TreeModel::iterator iter = selection->get_selected();
    if(iter) {  //wurde eine Auswahl getroffen?
    	Gtk::TreeModel::Row row = *iter;
    
    	//Wir sind im Konkurrenzkampf, also wird der Preis halbiert
    	row[cols.col_price] = row[cols.col_price] / 2;
    }
    

    Hat man jedoch Mehrfachauswahl eingestellt, wird die Geschichte ein wenig komplizierter, da man eine callback-Funktion erstellen muss:

    selection->selected_foreach_iter( sigc::mem_fun(*this, &Application::handle_multiple_selection) );
    
    void Application::handle_multiple_selection(const Gtk::TreeModel::iterator &iter) {
    	Gtk::TreeModel::Row row = *iter;
    
    	//Wir sind immer noch im Konkurrenzkampf, also wird der Preis ueber die ganze Selektion halbiert ;-)
    	row[cols.col_price] = row[cols.col_price] / 2;
    }
    

    Dieser Code bewirkt, dass die Methode handle_multiple_selection für jedes Element der Auswahl aufgerufen und abgearbeitet wird.

    Im Prinzip haben wir nun durch die "Preisveränderungen" auch schon gesehen, wie man auf die Daten zugreift. Der Weg ist immer der gleiche:

    • Selection-Objekt holen
    • Iterator auf Element(e) holen (egal ob per callback oder direkt)
    • Ein Gtk::TreeModel:Row erstellen
    • Daten lesen/ändern

    1.4 Gtk::Cellrenderer

    Bisher haben wir in unseren Listen/Bäumen immer nur Text, Zahlen oder auch mal eine CheckBox angezeigt. Nun bietet gtkmm über die Klasse Gtk::CellRenderer (und den Derivaten) diverse Möglichkeiten, Informationen darzustellen. Seien es nun Bilder, CheckBoxen, SpinButtons oder Fortschrittsanzeigen. Eine Übersicht findet man unter [3].

    Ich möchte hier am Beispiel einer Liste zeigen, wie das geht.

    #include <gtkmm.h> 
    
    class Application : public Gtk::Window { 
    	//Wir definieren hier unsere Spalten bzw. wie eine Zeile aussieht
        class Columns : public Gtk::TreeModel::ColumnRecord { 
    	public: 
            Columns() { 
                add(col_id); 
                add(col_colour); 
    		} 
    
            ~Columns() {} 
    
            Gtk::TreeModelColumn<unsigned> col_id; 
            Gtk::TreeModelColumn<Glib::ustring> col_colour; 
    	}; 
    
        Gtk::VBox topBox; 
    
    	//Hier die drei wichtigsten Komponenten
        Gtk::ScrolledWindow scrollWindow; 
        Gtk::TreeView treeView; 
        Glib::RefPtr<Gtk::ListStore> listStore; 
    
        Columns cols; 
    
    	public: 
            Application() { 
            set_title("ListStore Example"); 
            set_default_size(200,200); 
    
    		//Wir erstellen das ListStore und weisen es dem TreeView zu
            treeView.set_model(listStore = Gtk::ListStore::create(cols)); 
    
    		//Wir fuegen noch ein paar Daten ein
            Gtk::TreeModel::Row row = *(listStore->append()); 
            row[cols.col_id] = 0; 
            row[cols.col_colour] = "red"; 
    
            row = *(listStore->append()); 
            row[cols.col_id] = 1; 
            row[cols.col_colour] = "blue"; 
    
            row = *(listStore->append()); 
            row[cols.col_id] = 2; 
            row[cols.col_colour] = "green"; 
    
            row = *(listStore->append()); 
            row[cols.col_id] = 3; 
            row[cols.col_colour] = "white"; 
    
            treeView.append_column("Id", cols.col_id); 
    
    		//Die Spalte Colour soll farbig dargestellt werden 
            //Zuerst legen wir einen entsprechenden CellRenderer an
            Gtk::CellRendererText *cell = new Gtk::CellRendererText; 
    
    		//Wir fuegen die Spalte hinzu
            int column_count = treeView.append_column("Colour", *cell); 
    
            //Dann holen wir uns die gerade hinzugefuegte Spalte
            Gtk::TreeViewColumn *column = treeView.get_column(column_count-1); 
    
    		//Und sagen dann, woher die Informationen kommen und
    		//wie sie dargestellt werden sollen
            if (column) { 
    #ifndef GLIBMM_PROPERTIES_ENABLED 
                column->add_attribute(cell->property_text(), cols.col_colour); 
                column->add_attribute(cell->property_background(), cols.col_colour); 
    #else 
                column->add_attribute(*cell, "text", cols.col_colour); 
                column->add_attribute(*cell, "background", cols.col_colour); 
    #endif 
            } 
    
            scrollWindow.add(treeView); 
            scrollWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); 
    
            topBox.pack_start(scrollWindow); 
            add(topBox); 
    
            show_all_children(); 
    	} 
        ~Application() { } 
    }; 
    
    int main(int argc, char **argv) { 
        Gtk::Main m(argc, argv); 
    
        Application app; 
        m.run(app); 
    
        return EXIT_SUCCESS; 
    }
    

    Ergebnis:

    An sich hat sich unser Programm nicht groß verändert, nur das Hinzufügen der letzten Spalte ist neu. Natürlich hat jeder der verschiedenen Gtk::CellRenderer sein eigenes Property, welches man setzen muss, damit die Daten angezeigt werden. Jedoch greift einem die Dokumentation hier unter die Arme und sagt, welches Property gesetzt werden muss.

    2 Die "Gtk::ComboBoxen"

    gtkmm bietet vier ComboBoxen an: Gtk::ComboBox, Gtk::ComboBoxText, Gtk::ComboBoxEntry und Gtk::ComboBoxEntryText, wobei Gtk::ComboBox die Basisklasse und die anderen drei ComboBoxen Spezialisierungen darstellen. Für Gtk::ComboBox und Gtk::ComboBoxEntry muss man zuerst ein Gtk::TreeModel erstellen, bevor man Einträge hinzufügen kann (hier wird auch die Gemeinsamkeit mit Gtk::TreeView deutlich, auf die ich bereits einführend hingewiesen habe). Bei den anderen beiden hingegen benutzt man append_text um Einträge einzufügen.

    Nun, schauen wir uns die einzelnen Komponenten an.

    2.1 Gtk::ComboBox und Gtk::ComboBoxText

    Die Vorgehensweise bei einer Gtk::ComboBox ist fast gleich wie bei einem Gtk::TreeView:

    #include <iostream>
    #include <gtkmm.h>
    
    class Application : public Gtk::Window {
    	//Wir definieren hier unsere Spalten bzw. wie eine Zeile aussieht
    	class Columns : public Gtk::TreeModel::ColumnRecord {
    	public:
    		Columns() {
    			add(col_id);
    			add(col_name);
    			add(col_market_share);
            } 
    
    		~Columns() {}
    
    		Gtk::TreeModelColumn<unsigned> col_id;
    		Gtk::TreeModelColumn<Glib::ustring> col_name;
    		Gtk::TreeModelColumn<int> col_market_share;
        };
    
    	Gtk::VBox topBox;
    
    	Gtk::ComboBox comboBox;
    	Glib::RefPtr<Gtk::TreeStore> treeStore;
    
    	Columns cols;
    
    	protected:
    	//Signalhandler der reagiert, sobald sich die Auswahl aendert
    	virtual void on_combo_changed() {
    		Gtk::TreeModel::iterator iter = comboBox.get_active();
    		if (iter) {
    			Gtk::TreeModel::Row row = *iter;
    			if (row) {
    				std::cout	<<"ID: "<<row[cols.col_id]<<'\n'
    							<<"Name: "<<row[cols.col_name]<<'\n'
    							<<"Marktanteil: "<<row[cols.col_market_share]<<std::endl;
                }
            }
        }
    
    	public:
    	Application() {
    		set_title("Gtk::ComboBox Example");
    		set_default_size(300,25);
    
    		//Wir erstellen das TreeStore und weisen es dem TreeView zu
    		comboBox.set_model(treeStore = Gtk::TreeStore::create(cols));
    
    		//Wir fuegen noch ein paar Daten ein
    		//Ein Knoten auf der ersten Ebene
    		Gtk::TreeModel::Row row = *(treeStore->append());
    		row[cols.col_id] = 2000;
    		row[cols.col_name] = "Elektrogeräte";
    		row[cols.col_market_share] = 23;
    
    		//Drei Subknoten
    		Gtk::TreeModel::Row childrow = *(treeStore->append(row.children()));
    		childrow[cols.col_id] = 2485;
    		childrow[cols.col_name] = "Staubsauger";
    		childrow[cols.col_market_share] = 40;
    
    		childrow = *(treeStore->append(row.children()));
    		childrow[cols.col_id] = 2751;
    		childrow[cols.col_name] = "Mixer";
    		childrow[cols.col_market_share] = 10;
    
    		childrow = *(treeStore->append(row.children()));
    		childrow[cols.col_id] = 2982;
    		childrow[cols.col_name] = "Waschmaschine";
    		childrow[cols.col_market_share] = 20;
    
    		//Wieder ein Knoten auf Ebene 1
    		row = *(treeStore->append());
    		row[cols.col_id] = 3000;
    		row[cols.col_name] = "Reinigungsmittel";
    		row[cols.col_market_share] = 32;
    
    		//Und drei Subknoten
    		childrow = *(treeStore->append(row.children()));
    		childrow[cols.col_id] = 3149;
    		childrow[cols.col_name] = "Brof";
    		childrow[cols.col_market_share] = 12;
    
    		childrow = *(treeStore->append(row.children()));
    		childrow[cols.col_id] = 3587;
    		childrow[cols.col_name] = "Kill it!";
    		childrow[cols.col_market_share] = 19;
    
    		childrow = *(treeStore->append(row.children()));
    		childrow[cols.col_id] = 2982;
    		childrow[cols.col_name] = "Uber-Cleaner";
    		childrow[cols.col_market_share] = 67;
    
    		//Wir fuegen die Spalten hinzu
    		comboBox.pack_start(cols.col_name);
    
    		//Die Spalte Marktanteil soll als Fortschrittsbalken dargestellt werden
    		//Zuerst legen wir einen entsprechenden CellRenderer an
    		Gtk::CellRendererProgress *cell = new Gtk::CellRendererProgress;
    
    		//Wir fuegen den CellRenderer bzw. die Spalte hinzu
    		comboBox.pack_start(*cell);
    
    		//Und sagen dann, woher die Informationen kommen, die dargestellt werden
    		if (cell) {
    #ifndef GLIBMM_PROPERTIES_ENABLED
    			comboBox.add_attribute(cell->property_value(), cols.col_market_share);
    #else
    			comboBox.add_attribute(*cell, "value", cols.col_market_share);
    #endif
            }
    
    		//Erstes Element soll vorselektiert sein
    		comboBox.set_active(0);
    
    		//Verknuepfung des Signalhandlers
    		comboBox.signal_changed().connect(sigc::mem_fun(*this, &Application::on_combo_changed));
    
    		//Hinzufuegen der Komponenten
    		topBox.pack_start(comboBox);
    		add(topBox);
    
    		show_all_children();
        }
    	~Application() { }
    };
    
    int main(int argc, char **argv) {
    	Gtk::Main m(argc, argv);
    
    	Application app;
    	m.run(app);
    
    	return EXIT_SUCCESS;
    }
    

    Nun ja, alles in allem sieht das jetzt so alleine auf weiter Flur nicht besonders schön aus, aber es zeigt, was alles mit Gtk::ComboBox möglich ist. Gleichzeitig zeigt es aber auch, dass dieses Widget in den allermeisten Fällen totaler Overkill ist, da man häufig nur eine einzige Spalte mit Text für die Einträge braucht. Hier kommt Gtk::ComboBoxText ins Spiel:

    #include <iostream>
    #include <gtkmm.h>
    
    class Application : public Gtk::Window {
    	Gtk::VBox topBox;
    	Gtk::ComboBoxText comboBox;
    
    	protected:
    	//Signalhandler der reagiert, sobald sich die Auswahl aendert
    	virtual void on_combo_changed() {
    		std::cout<<"Auswahl: "<<comboBox.get_active_text()<<std::endl;
        }
    
    	public:
    	Application() {
    		set_title("Gtk::ComboBoxText Example");
    		set_default_size(220,25);
    
    		//Wir fuegen noch ein paar Daten ein
    		comboBox.append_text("Led Zeppelin");
    		comboBox.append_text("Deep Purple");
    		comboBox.append_text("The Doors");
    
    		//Erstes Element soll vorselektiert sein
    		comboBox.set_active(0);
    
    		//Verknuepfung des Signalhandlers
    		comboBox.signal_changed().connect(sigc::mem_fun(*this, &Application::on_combo_changed));
    
    		//Hinzufuegen der Komponenten
    		topBox.pack_start(comboBox);
    		add(topBox);
    
    		show_all_children();
        }
    	~Application() { }
    };
    
    int main(int argc, char **argv) {
    	Gtk::Main m(argc, argv);
    
    	Application app;
    	m.run(app);
    
    	return EXIT_SUCCESS;
    }
    

    Wie man sieht, hat sich die ganze Geschichte dadurch erheblich vereinfacht. 🙂

    2.2 Gtk::ComboBoxEntry und Gtk::ComboBoxEntryText

    Die in diesem Abschnitt behandelten ComboBoxen unterscheiden sich wie gesagt nur in einem Punkt von den anderen beiden: Sie ermöglichen die Eingabe von Text via Textfeld, um so auch andere Werte als die vorgegebenen anzugeben.

    Zuerst ein Beispiel mit dem Gtk::ComboBoxEntry-Widget:

    #include <iostream>
    #include <gtkmm.h>
    
    class Application : public Gtk::Window {
    	//Wir definieren hier unsere Spalten bzw. wie eine Zeile aussieht
    	class Columns : public Gtk::TreeModel::ColumnRecord {
    	public:
    		Columns() {
    			add(col_id);
    			add(col_name);
    			add(col_price);
    			add(col_stock);
            } 
    
    		~Columns() {}
    
    		Gtk::TreeModelColumn<unsigned> col_id;
    		Gtk::TreeModelColumn<Glib::ustring> col_name;
    		Gtk::TreeModelColumn<float> col_price;
    		Gtk::TreeModelColumn<bool> col_stock;
        };
    
    	Gtk::VBox topBox;
    
    	//Hier die drei wichtigsten Komponenten
    	Gtk::ScrolledWindow scrollWindow;
    	Gtk::ComboBoxEntry comboBox;
    	Glib::RefPtr<Gtk::ListStore> listStore;
    
    	Columns cols;
    
    	protected:
    	//Signalhandler der reagiert, sobald sich die Auswahl aendert
    	void on_combo_changed() {
    		std::cout<<"Auswahl: "<<(comboBox.get_entry())->get_text()<<std::endl;
        }
    
    	public:
    	Application() {
    		set_title("ComboBoxEntry Example");
    		set_default_size(350,25);
    
    		//Wir erstellen das ListStore und weisen es dem TreeView zu
    		comboBox.set_model(listStore = Gtk::ListStore::create(cols));
    
    		//Wir fuegen noch ein paar Daten ein
    		Gtk::TreeModel::Row row = *(listStore->append());
    		row[cols.col_id] = 2485;
    		row[cols.col_name] = "Staubsauger";
    		row[cols.col_price] = 79.99f;
    		row[cols.col_stock] = true;
    
    		row = *(listStore->append());
    		row[cols.col_id] = 2751;
    		row[cols.col_name] = "Mixer";
    		row[cols.col_price] = 25.75f;
    		row[cols.col_stock] = true;
    
    		row = *(listStore->append());
    		row[cols.col_id] = 2982;
    		row[cols.col_name] = "Waschmaschine";
    		row[cols.col_price] = 699.99f;
    		row[cols.col_stock] = false;
    
    		//Nun muessen wir noch angeben, welche Spalten angezeigt werden sollen
    		comboBox.pack_start(cols.col_id);
    		comboBox.pack_start(cols.col_price);
    		comboBox.pack_start(cols.col_stock);
    
    		//Auf welche Spalte sich die Eingabe beziehen soll...
    		comboBox.set_text_column(cols.col_name);
    
    		comboBox.signal_changed().connect(sigc::mem_fun(*this, &Application::on_combo_changed));
    
    		//Wir weisen die ComboBox dem ScrollWindow zu
    		topBox.pack_start(comboBox);
    		add(topBox);
    
    		show_all_children();
        }
    	~Application() { }
    };
    
    int main(int argc, char **argv) {
    	Gtk::Main m(argc, argv);
    
    	Application app;
    	m.run(app);
    
    	return EXIT_SUCCESS;
    }
    

    Das einzige, was man bei Gtk::ComboBoxEntry beachten muss, ist dass man per m_combo.set_text_column(Columns::col_name); angeben muss, welche Spalte den Text beinhaltet. Diese Spalte wird bei diesem Aufruf auch gleich zum Gtk::ComboBoxEntry-Widget hinzugefügt. Das heißt, man sollte nicht auch noch pack_start auf diese Spalte anwenden, weil sie sonst zweimal vorkommt.

    Abschließend wollen wir uns noch das Gtk::ComboBoxEntryText-Widget anschauen:

    #include <iostream>
    #include <gtkmm.h>
    
    class Application : public Gtk::Window {
    	Gtk::VBox topBox;
    	Gtk::ComboBoxEntryText comboBox;
    
    	protected:
    	//Signalhandler der reagiert, sobald sich die Auswahl aendert
    	virtual void on_combo_changed() {
    		std::cout<<"Auswahl: "<<(comboBox.get_entry())->get_text()<<std::endl;
        }
    
    	public:
    	Application() {
    		set_title("Gtk::ComboBoxEntryText Example");
    		set_default_size(220,25);
    
    		//Wir fuegen noch ein paar Daten ein
    		comboBox.append_text("Led Zeppelin");
    		comboBox.append_text("Deep Purple");
    		comboBox.append_text("The Doors");
    
    		//Verknuepfung des Signalhandlers
    		comboBox.signal_changed().connect(sigc::mem_fun(*this, &Application::on_combo_changed));
    
    		//Hinzufuegen der Komponenten
    		topBox.pack_start(comboBox);
    		add(topBox);
    
    		show_all_children();
        }
    	~Application() { }
    };
    
    int main(int argc, char **argv) {
    	Gtk::Main m(argc, argv);
    
    	Application app;
    	m.run(app);
    
    	return EXIT_SUCCESS;
    }
    

    3 Referenzen

    [1] Tutorial zu Gtk::TreeView
    [2] Gtk::TreeView Übersicht
    [3] Gtk::CellRenderer Hierarchie
    [4] Tutorial zu den Gtk::ComboBoxen



  • Hallo,

    weißt du (oder jemand anders), wie man schnell und einfach die "liststore"-spalten durch 'nen klick sortieren kann?
    Ich find das fehltin dem beispiel, sonst ein fettes:

    Danke!



  • In dem von mir unter [1] verlinkten Tutorial findet sich ein Abschnitt darüber: http://gtkmm.org/docs/gtkmm-2.4/docs/tutorial/html/sec-treeview-sort.html



  • Hab ich total überlesen, danke!


Log in to reply