The View
| Chapter 10. ListView, GridView, ColumnView |

## The View

The View is the widget that displays the model data and allows the user to interact with it. The View can show all of the model's columns, or just some, and it can show them in various ways.

An important requirement for views (especially views of long lists) is that they need to know which items are not visible so they can be recycled. Views achieve that by implementing the `Scrollable` interface and expecting to be placed directly into a `ScrolledWindow`.

There are different view widgets to choose from.

### ListView

The `ListView` shows a 1-dimensional list with one column.

Reference

#### Example

Figure 10.1. ListView

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_label(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item);

      // Child widgets:
      Gtk::Box m_VBox;
      Gtk::Label m_Heading;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::ListView m_ListView;
      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;

      Glib::RefPtr<Gtk::StringList> m_StringList;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Heading("<b>Name</b>", Gtk::Align::START),
      m_Button_Quit("Quit")
    {
      set_title("Gtk::ListView (Gtk::StringList) example");
      set_default_size(300, 200);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      // Add the ListView, inside a ScrolledWindow, with the heading above
      // and the button underneath.
      m_Heading.set_use_markup();
      m_VBox.append(m_Heading);
      m_ScrolledWindow.set_child(m_ListView);

      // 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 list model:
      m_StringList = Gtk::StringList::create({"Billy Bob", "Joey Jojo", "Rob McRoberts"});

      // Set list model and selection model.
      auto selection_model = Gtk::SingleSelection::create(m_StringList);
      selection_model->set_autoselect(false);
      selection_model->set_can_unselect(true);
      m_ListView.set_model(selection_model);
      m_ListView.add_css_class("data-table"); // high density table

      // Add the factory for the ListView's single column.
      auto factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_setup_label));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_name));
      m_ListView.set_factory(factory);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::on_setup_label(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      list_item->set_child(*Gtk::make_managed<Gtk::Label>("", Gtk::Align::START));
    }

    void ExampleWindow::on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto pos = list_item->get_position();
      if (pos == GTK_INVALID_LIST_POSITION)
        return;
      auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
      if (!label)
        return;
      label->set_text(m_StringList->get_string(pos));
    }

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

### GridView

The `GridView` shows a 2-dimensional grid.

Reference

#### Example

Figure 10.2. GridView

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>
    #include <gdkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      class ModelColumns;
      // Signal handlers:
      void on_button_quit();
      void on_item_activated(unsigned int position);
      void on_selection_changed();
      void on_setup_listitem(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_listitem(const Glib::RefPtr<Gtk::ListItem>& list_item);
      int on_model_sort(const Glib::RefPtr<const ModelColumns>& a, const Glib::RefPtr<const ModelColumns>& b);

      void add_entry(const std::string& filename, const Glib::ustring& description);

      // A Gio::ListStore stores filename, description and texture.
      class ModelColumns : public Glib::Object
      {
      public:
        std::string m_filename;
        Glib::ustring  m_description;
        Glib::RefPtr<Gdk::Texture> m_texture;

        static Glib::RefPtr<ModelColumns> create(const std::string& filename,
          const Glib::ustring& description, const Glib::RefPtr<Gdk::Texture>& texture)
        {
          return Glib::make_refptr_for_instance<ModelColumns>(
            new ModelColumns(filename, description, texture));
        }

      protected:
        ModelColumns(const std::string& filename, const Glib::ustring& description,
          const Glib::RefPtr<Gdk::Texture>& texture)
        : m_filename(filename), m_description(description), m_texture(texture)
        { }
      }; // ModelColumns

      Glib::RefPtr<Gio::ListStore<ModelColumns>> m_data_model;
      Glib::RefPtr<Gtk::SingleSelection> m_selection_model;
      Glib::RefPtr<Gtk::SignalListItemFactory> m_factory;

      // Child widgets:
      Gtk::Box m_VBox;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::GridView m_GridView;
      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <array>
    #include <iostream>

    namespace
    {
      struct GridEntry
      {
        std::string m_filename;
        Glib::ustring m_description;
      };

      std::array<GridEntry, 8> entries =
      {
        GridEntry{"mozilla-firefox.png", "<b>Mozilla Firefox</b> Logo"},
        GridEntry{"xmms.xpm",            "<b>XMMS</b> Logo"},
        GridEntry{"gnome-dice-1.svg",    "<b>Gnome Dice 1</b> Logo"},
        GridEntry{"gnome-dice-2.svg",    "<b>Gnome Dice 2</b> Logo"},
        GridEntry{"gnome-dice-3.svg",    "<b>Gnome Dice 3</b> Logo"},
        GridEntry{"gnome-dice-4.svg",    "<b>Gnome Dice 4</b> Logo"},
        GridEntry{"gnome-dice-5.svg",    "<b>Gnome Dice 5</b> Logo"},
        GridEntry{"gnome-dice-6.svg",    "<b>Gnome Dice 6</b> Logo"}
      };

    } // anonymous namespace

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Quit("Quit")
    {
      set_title("Gtk::GridView (Gio::ListStore) example");
      set_default_size(400, 400);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      // Add the GridView inside a ScrolledWindow, with the button underneath:
      m_ScrolledWindow.set_child(m_GridView);

      // 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(6);
      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 data model:
      m_data_model = Gio::ListStore<ModelColumns>::create();
      m_selection_model = Gtk::SingleSelection::create(m_data_model);
      m_selection_model->set_autoselect(false);

      m_factory = Gtk::SignalListItemFactory::create();
      m_factory->signal_setup().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_setup_listitem));
      m_factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_listitem));

      // Fill the Gio::ListStore's data model and sort it.
      for (const auto& entry : entries)
        add_entry(entry.m_filename, entry.m_description);
      m_data_model->sort(sigc::mem_fun(*this, &ExampleWindow::on_model_sort));

      m_GridView.set_model(m_selection_model);
      m_GridView.set_factory(m_factory);

      m_GridView.signal_activate().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_item_activated));
      m_selection_model->property_selected().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_selection_changed));
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::on_setup_listitem(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      // Each ListItem contains a vertical Box with an Image and a Label.
      auto vBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
      auto picture = Gtk::make_managed<Gtk::Picture>();
      picture->set_can_shrink(false);
      picture->set_halign(Gtk::Align::CENTER);
      picture->set_valign(Gtk::Align::END);
      vBox->append(*picture);
      vBox->append(*Gtk::make_managed<Gtk::Label>());
      list_item->set_child(*vBox);
    }

    void ExampleWindow::on_bind_listitem(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto vBox = dynamic_cast<Gtk::Box*>(list_item->get_child());
      if (!vBox)
        return;
      auto picture = dynamic_cast<Gtk::Picture*>(vBox->get_first_child());
      if (!picture)
        return;
      auto label = dynamic_cast<Gtk::Label*>(picture->get_next_sibling());
      if (!label)
        return;
      picture->set_paintable(col->m_texture);
      label->set_markup(col->m_description);
    }

    void ExampleWindow::on_item_activated(unsigned int position)
    {
      auto col = m_data_model->get_item(position);
      if (!col)
        return;

      const std::string filename = col->m_filename;
      const Glib::ustring description = col->m_description;

      std::cout  << "Item activated: filename=" << filename
        << ", description=" << description << std::endl;
    }

    void ExampleWindow::on_selection_changed()
    {
      auto position = m_selection_model->get_selected();
      auto col = m_data_model->get_item(position);
      if (!col)
        return;

      const std::string filename = col->m_filename;
      const Glib::ustring description = col->m_description;

      std::cout  << "Selection Changed to: filename=" << filename
        << ", description=" << description << std::endl;
    }

    int ExampleWindow::on_model_sort(const Glib::RefPtr<const ModelColumns>& a,
      const Glib::RefPtr<const ModelColumns>& b)
    {
      return (a->m_description < b->m_description) ? -1 : ((a->m_description > b->m_description) ? 1 : 0);
    }

    void ExampleWindow::add_entry(const std::string& filename,
            const Glib::ustring& description )
    {
      try
      {
         auto pixbuf = Gdk::Pixbuf::create_from_file(filename);
         auto texture = Gdk::Texture::create_for_pixbuf(pixbuf);
         m_data_model->append(ModelColumns::create(filename, description, texture));
      }
      catch (const Gdk::PixbufError& ex)
      {
        std::cerr << "Gdk::PixbufError: " << ex.what() << std::endl;
      }
      catch (const Glib::FileError& ex)
      {
        std::cerr << "Glib::FileError: " << ex.what() << std::endl;
      }
    }

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

### ColumnView

The `ColumnView` shows a 1-dimensional list with one or more columns.

Reference

#### Example

Figure 10.3. ColumnView

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_label(const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign);
      void on_setup_progressbar(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);
      void on_bind_number(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_percentage(const Glib::RefPtr<Gtk::ListItem>& list_item);

      // A Gio::ListStore item.
      class ModelColumns : public Glib::Object
      {
      public:
        unsigned int m_col_id;
        Glib::ustring m_col_name;
        short m_col_number;
        int m_col_percentage;

        static Glib::RefPtr<ModelColumns> create(unsigned int col_id,
          const Glib::ustring& col_name, short col_number, int col_percentage)
        {
          return Glib::make_refptr_for_instance<ModelColumns>(
            new ModelColumns(col_id, col_name, col_number, col_percentage));
        }

      protected:
        ModelColumns(unsigned int col_id, const Glib::ustring& col_name,
          short col_number, int col_percentage)
        : m_col_id(col_id), m_col_name(col_name), m_col_number(col_number),
          m_col_percentage(col_percentage)
        {}
      }; // 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<Gio::ListStore<ModelColumns>> m_ListStore;
    };

    #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 (Gio::ListStore) example");
      set_default_size(500, 250);

      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 List model:
      m_ListStore = Gio::ListStore<ModelColumns>::create();
      m_ListStore->append(ModelColumns::create(1, "Billy Bob", 10, 15));
      m_ListStore->append(ModelColumns::create(2, "Joey Jojo", 20, 40));
      m_ListStore->append(ModelColumns::create(3, "Rob McRoberts", 30, 70));

      // Set list model and selection model.
      auto selection_model = Gtk::SingleSelection::create(m_ListStore);
      selection_model->set_autoselect(false);
      selection_model->set_can_unselect(true);
      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::bind(sigc::mem_fun(*this,
        &ExampleWindow::on_setup_label), Gtk::Align::END));
      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::bind(sigc::mem_fun(*this,
        &ExampleWindow::on_setup_label), Gtk::Align::START));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_name));
      column = Gtk::ColumnViewColumn::create("Name", factory);
      m_ColumnView.append_column(column);

      // Number column
      factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
        &ExampleWindow::on_setup_label), Gtk::Align::END));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_number));
      column = Gtk::ColumnViewColumn::create("Formatted number", factory);
      m_ColumnView.append_column(column);

      // Percentage column
      factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_setup_progressbar));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_percentage));
      column = Gtk::ColumnViewColumn::create("Some percentage", factory);
      m_ColumnView.append_column(column);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::on_setup_label(
      const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign)
    {
      list_item->set_child(*Gtk::make_managed<Gtk::Label>("", halign));
    }

    void ExampleWindow::on_setup_progressbar(
      const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto progressbar = Gtk::make_managed<Gtk::ProgressBar>();
      progressbar->set_show_text(true);
      list_item->set_child(*progressbar);
    }

    void ExampleWindow::on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto label = dynamic_cast<Gtk::Label*>(list_item->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 col = std::dynamic_pointer_cast<ModelColumns>(list_item->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);
    }

    void ExampleWindow::on_bind_number(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
      if (!label)
        return;
      // 10 digits, using leading zeroes.
      label->set_text(Glib::ustring::sprintf("%010d", col->m_col_number));
    }

    void ExampleWindow::on_bind_percentage(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto progressbar = dynamic_cast<Gtk::ProgressBar*>(list_item->get_child());
      if (!progressbar)
        return;
      progressbar->set_fraction(col->m_col_percentage * 0.01);
    }

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

The Factory |  | Sorting