While the deprecated TreeView
provided built-in support for trees,
the list widgets, and in particular Gio::ListModel
, do not.
However, gtkmm provides functionality to make trees look and behave like lists for
the people who still want to display lists. This is achieved by using the
TreeListModel
to flatten a tree into a list.
The TreeExpander
widget can then be used inside a listitem
to allow users to expand and collapse rows.
File: examplewindow.h
(For use with gtkmm 4)
#ifndef GTKMM_EXAMPLEWINDOW_H #define GTKMM_EXAMPLEWINDOW_H #include <gtkmm.h> class ExampleWindow : public Gtk::Window { public: ExampleWindow(); ~ExampleWindow() override; protected: // Signal handlers: void on_button_quit(); void on_setup_id(const Glib::RefPtr<Gtk::ListItem>& list_item); void on_setup_name(const Glib::RefPtr<Gtk::ListItem>& list_item); void on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item); void on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item); Glib::RefPtr<Gio::ListModel> create_model( const Glib::RefPtr<Glib::ObjectBase>& item = {}); // A Gio::ListStore item. class ModelColumns : public Glib::Object { public: int m_col_id; Glib::ustring m_col_name; static Glib::RefPtr<ModelColumns> create(int col_id, const Glib::ustring& col_name) { return Glib::make_refptr_for_instance<ModelColumns>( new ModelColumns(col_id, col_name)); } protected: ModelColumns(int col_id, const Glib::ustring& col_name) : m_col_id(col_id), m_col_name(col_name) {} }; // ModelColumns // Child widgets: Gtk::Box m_VBox; Gtk::ScrolledWindow m_ScrolledWindow; Gtk::ColumnView m_ColumnView; Gtk::Box m_ButtonBox; Gtk::Button m_Button_Quit; Glib::RefPtr<Gtk::TreeListModel> m_TreeListModel; }; #endif //GTKMM_EXAMPLEWINDOW_H
File: examplewindow.cc
(For use with gtkmm 4)
#include "examplewindow.h" ExampleWindow::ExampleWindow() : m_VBox(Gtk::Orientation::VERTICAL), m_Button_Quit("Quit") { set_title("Gtk::ColumnView (Gtk::TreeListModel) example"); set_default_size(300, 350); m_VBox.set_margin(5); set_child(m_VBox); // Add the ColumnView, inside a ScrolledWindow, with the button underneath: m_ScrolledWindow.set_child(m_ColumnView); // Only show the scrollbars when they are necessary: m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC); m_ScrolledWindow.set_expand(); m_VBox.append(m_ScrolledWindow); m_VBox.append(m_ButtonBox); m_ButtonBox.append(m_Button_Quit); m_ButtonBox.set_margin(5); m_Button_Quit.set_hexpand(true); m_Button_Quit.set_halign(Gtk::Align::END); m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this, &ExampleWindow::on_button_quit)); // Create the root model: auto root = create_model(); // Set list model and selection model. // passthrough must be false when Gtk::TreeExpander is used in the view. m_TreeListModel = Gtk::TreeListModel::create(root, sigc::mem_fun(*this, &ExampleWindow::create_model), /* passthrough */ false, /* autoexpand */ false); auto selection_model = Gtk::MultiSelection::create(m_TreeListModel); m_ColumnView.set_model(selection_model); m_ColumnView.add_css_class("data-table"); // high density table // Make the columns reorderable. // This is not necessary, but it's nice to show the feature. m_ColumnView.set_reorderable(true); // Add the ColumnView's columns: // Id column auto factory = Gtk::SignalListItemFactory::create(); factory->signal_setup().connect( sigc::mem_fun(*this, &ExampleWindow::on_setup_id)); factory->signal_bind().connect( sigc::mem_fun(*this, &ExampleWindow::on_bind_id)); auto column = Gtk::ColumnViewColumn::create("ID", factory); m_ColumnView.append_column(column); // Name column factory = Gtk::SignalListItemFactory::create(); factory->signal_setup().connect( sigc::mem_fun(*this, &ExampleWindow::on_setup_name)); factory->signal_bind().connect( sigc::mem_fun(*this, &ExampleWindow::on_bind_name)); column = Gtk::ColumnViewColumn::create("Name", factory); m_ColumnView.append_column(column); } ExampleWindow::~ExampleWindow() { } void ExampleWindow::on_button_quit() { set_visible(false); } Glib::RefPtr<Gio::ListModel> ExampleWindow::create_model( const Glib::RefPtr<Glib::ObjectBase>& item) { auto col = std::dynamic_pointer_cast<ModelColumns>(item); auto result = Gio::ListStore<ModelColumns>::create(); if (!col) { // Top names result->append(ModelColumns::create(1, "Billy Bob")); result->append(ModelColumns::create(2, "Joey Jojo")); result->append(ModelColumns::create(3, "Rob McRoberts")); } else { switch (col->m_col_id) { case 1: result->append(ModelColumns::create(11, "Billy Bob Junior")); result->append(ModelColumns::create(12, "Sue Bob")); break; case 3: result->append(ModelColumns::create(31, "Xavier McRoberts")); break; } } // If result is empty, it's a leaf in the tree, i.e. an item without children. // Returning an empty RefPtr (not a RefPtr with an empty Gio::ListModel) // signals that the item is not expandable. return (result->get_n_items() > 0) ? result : Glib::RefPtr<Gio::ListModel>(); } void ExampleWindow::on_setup_id( const Glib::RefPtr<Gtk::ListItem>& list_item) { // Each ListItem contains a TreeExpander, which contains a Label. // The Label shows the ModelColumns::m_col_id. That's done in on_bind_id(). auto expander = Gtk::make_managed<Gtk::TreeExpander>(); auto label = Gtk::make_managed<Gtk::Label>(); label->set_halign(Gtk::Align::END); expander->set_child(*label); list_item->set_child(*expander); } void ExampleWindow::on_setup_name( const Glib::RefPtr<Gtk::ListItem>& list_item) { list_item->set_child(*Gtk::make_managed<Gtk::Label>("", Gtk::Align::START)); } void ExampleWindow::on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item) { // When TreeListModel::property_passthrough() is false, ListItem::get_item() // is a TreeListRow. TreeExpander needs the TreeListRow. // The ModelColumns item is returned by TreeListRow::get_item(). auto row = std::dynamic_pointer_cast<Gtk::TreeListRow>(list_item->get_item()); if (!row) return; auto col = std::dynamic_pointer_cast<ModelColumns>(row->get_item()); if (!col) return; auto expander = dynamic_cast<Gtk::TreeExpander*>(list_item->get_child()); if (!expander) return; expander->set_list_row(row); auto label = dynamic_cast<Gtk::Label*>(expander->get_child()); if (!label) return; label->set_text(Glib::ustring::format(col->m_col_id)); } void ExampleWindow::on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item) { auto row = std::dynamic_pointer_cast<Gtk::TreeListRow>(list_item->get_item()); if (!row) return; auto col = std::dynamic_pointer_cast<ModelColumns>(row->get_item()); if (!col) return; auto label = dynamic_cast<Gtk::Label*>(list_item->get_child()); if (!label) return; label->set_text(col->m_col_name); }
File: main.cc
(For use with gtkmm 4)
#include "examplewindow.h" #include <gtkmm/application.h> int main(int argc, char* argv[]) { auto app = Gtk::Application::create("org.gtkmm.example"); // Shows the window and returns when it is closed. return app->make_window_and_run<ExampleWindow>(argc, argv); }