Displaying Trees
| Chapter 10. ListView, GridView, ColumnView |

## Displaying Trees

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.

TreeListModel Reference

TreeExpander Reference

### Example

Figure 10.6. TreeListModel

Source Code

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

Filtering |  | Chapter 11. The TreeView widget