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);
}