Gtkmm: Extra data in Combo Box made easy

By Jean Hertel, 7/31/20

gtkmm , gtk+ , combobox

Recently I did a big refactoring of adriconf and one of the things that I finally decided to fix was the combo box management. To configure any option in the hardware, we have to ask which options are supported and therefore we dynamically draw the configuration fields. Because of this it is necessary to dynamically locate which field we are changing and then update.

In HTML the Combo Box supports two values: a display text and the value itself of the field. I wanted the same kind of functionality using GTKmm, but the process is a little painful.

The ComboBox in GTKmm is very flexible, but that comes with a complexity. If you just want text you can use the ComboBoxText:

Gtk::ComboBoxText myCombo;

myCombo.append("Option 1");
myCombo.append("Ninjas!");
myCombo.append("Samurais");
myCombo.set_active(1);

// See selected option text
Glib::ustring text = myCombo.get_active_text();

This example seems quite simple, but to add extra fields, we need to do a much more complicated process. The official documentation already covers this well, so if you want to see in detail how to draw a field with more options or other things just see the documentation here.

In my case I wanted to have a pointer to the object I’m changing but without having to deal with all the model and rendering craziness. Because of that, I wrote the classes below:

#include "gtkmm.h"

template<class T>
class ExtraDataTreeModel : public Gtk::TreeModel::ColumnRecord {
public:
    Gtk::TreeModelColumn<Glib::ustring> displayName;
    Gtk::TreeModelColumn<T> extraData;

    ExtraDataTreeModel() {
        add(displayName);
        add(extraData);
    }
};

template<class T>
class ComboBoxExtra : public Gtk::ComboBox {
private:
    ExtraDataTreeModel<T> treeModel;

public:
    // Allow us to call Glade Builder
    ComboBoxExtra(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &refGlade) : Gtk::ComboBox(cobject) {
        auto listStore = Gtk::ListStore::create(treeModel);
        this->set_model(listStore);
        this->pack_start(treeModel.displayName);
    }

    ComboBoxExtra() : Gtk::ComboBox() {
        auto listStore = Gtk::ListStore::create(treeModel);
        this->set_model(listStore);
        this->pack_start(treeModel.displayName);
    }

    void append(const Glib::ustring &displayName, T extraData) {
        auto iter = static_cast<Gtk::ListStore *>(this->get_model().get())->append();
        auto row = *iter;
        row[treeModel.displayName] = displayName;
        row[treeModel.extraData] = extraData;
    }

    T get_active_extra_data() {
        auto iter = this->get_active();
        auto row = *iter;

        return row[treeModel.extraData];
    }

    void removeAllChildren() {
        auto listStore = dynamic_cast<Gtk::ListStore *>(this->get_model().get());
        listStore->clear();
    }
};

With these two classes we can create combo boxes with extra data in a very simple way:

MyCustomData *someCustomClassA, *someCustomClassB;

ComboBoxExtra<MyCustomData*> myCombo;

myCombo.append("A name to display", someCustomClassA);
myCombo.append("Another name", someCustomClassB);

// During the call to signal_changed()
MyCustomData* extraData = myCombo.get_active_extra_data();

Now we can take the pointers of the classes we are changing and update directly, no loops and no craziness.