Search code examples
c++gtkmm

GTKMM 4.10 - Scrolled window - set adjustments (horizontal + vertical) - not automatically scrolling


I am trying to programatically scroll to a particular row and column within a Gtk::ColumnView which is contained in a scrolled window. The below code does fetch and set the adjustments correctly (I verified this by printf statements) but the scrollbars do not move.

    void MyGtk::Scrolled_Window_Scroll_To_Common(
        Gtk::ScrolledWindow *scrolled,
        int row,
        int col,
        int number_rows,
        int number_cols
    )
    {
        //
        //First we must validate all parameters and terminate if any fail.
        //
        assert(scrolled);

        assert(row >= 0);

        assert(col >= 0);

        assert(number_rows > 0);

        assert(number_cols > 0);

        //
        //Now we need to fetch the horizontal and vertical adjustments for the
        //scrolled window.
        //
        auto h_adjustment = scrolled->get_hadjustment();
        assert(h_adjustment);

        auto v_adjustment = scrolled->get_vadjustment();
        assert(v_adjustment);

        //
        //Now we need to determnine the range for the horizontal scroll bar.
        //
        auto h_range =
            h_adjustment->get_upper() -
            h_adjustment->get_lower();

        assert(h_range >= 0);

        //
        //Now we need to determnine the range for the vertical scroll bar.
        //
        auto v_range =
            v_adjustment->get_upper() -
            v_adjustment->get_lower();

        assert(v_range >= 0);

        //
        //Now that we know the range for the horizontal scroll bar we can
        //determine the value to set the adjustment to based on range, the
        //column and total number of columns.
        //
        auto h_value =
            static_cast<double>(col + 1) /
            number_cols *
            h_range +
            h_adjustment->get_lower();

        assert(h_value >= 0);

        //
        //Now that we know the range for the vertical scroll bar we can
        //determine the value to set the adjustment to based on range, the
        //row and total number of columns.
        //
        auto v_value =
            static_cast<double>(row + 1) /
            number_rows *
            v_range +
            v_adjustment->get_lower();

        assert(v_value >= 0);

        //
        //Now that we have the horizontal value we can now adjust the value
        //for the adjustment.
        //
        h_adjustment->set_value(h_value);

        //
        //Now that we have the vertical value we can now adjust the value
        //for the adjustment.
        //
        v_adjustment->set_value(v_value);

        //
        //Now that we have changed the horizontal adjustment we need to set
        //it for the horizontal scroll bar.
        //
        scrolled->set_hadjustment(h_adjustment);

        //
        //Now that we have changed the vertical adjustment we need to set
        //it for the vertical scroll bar.
        //
        scrolled->set_vadjustment(v_adjustment);
    }

Do I need to force a redraw of the scrolled window ? Not really sure how to do this and thus would appreciate some help with the above code.

Searching for this on this forum did not lead to a result that worked.

Clarification

  1. I am converting from gtkmm 4.8 to 4.10.
  2. Have disabled deprecated functions for compile.
  3. The scroll_to() member existed in 4.8 and prior but was removed in 4.10
  4. An equivalent to scroll_to() was reinstated in 4.12.

Further research

It seems based on the example below that if the Gtk::ColumnView has more than one column then setting the adjustments does not scroll to the position as expected.

/* gtkmm example Copyright (C) 2023 gtkmm development team
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "columnview.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);
}


#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);
  void on_columnview_activate(guint position);

  // 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


#include "columnview.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));
  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));
  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));
  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));
  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));
  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));
  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));
  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));
  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));
  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));
  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));
  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);

  auto vadj = m_ScrolledWindow.get_vadjustment();
  vadj->set_value(vadj->get_upper());
  m_ScrolledWindow.set_vadjustment(vadj);
}

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

Note that I have fetched the vertical adjustment, set the value to the upper limit and then set this back to the scrolled window. However the scrolled window is on the first line and not the last.


Solution

  • Despite experimenting with the code and more research I did not work out how to adjust the scrollbars and make it work. However I did find a solution. My Linux distribution recently upgraded GTK to 4.12 (but not yet GTKMM which is still at 4.10) and the Gtk::ColumnView now has a scroll_to() member which does exactly the job that I wanted. So the following code is how I can now scroll to a row and column programmatically:

    //
    //Now we need to fetch the selection model for the column view so that we can
    //set the column to be selected.
    //
    auto model = view->get_model();
    assert(model);
    
    //
    //Now we need to select the row ROW for the model associated with
    //the columnview.
    //
    model->select_item(
      row,
      true
    );
    
    //
    //Now we need to obtain the gobject pointer that corresponds to the 
    //columnview so that we can use it in a GTK/C call.
    //
    auto g_columnview = view->gobj();
    assert(g_columnview);
    
    //
    //Now we have to fetch the set of columns associated with the columnview and
    //then ensure that it is valid.
    //
    auto columns = view->get_columns();
    assert(columns);
    
    //
    //Now we need to fetch the underlying object for the COL'th column
    //and ensure that a valid object was selected.  We can then use this
    //value in the scroll to function.
    //
    auto column =
    std::dynamic_pointer_cast<Gtk::ColumnViewColumn>(columns->get_object(col));
    
    assert(column);
    
    //
    //Now that we have the column in the columnview at position COL we now must
    //obtain the gobject pointer so that we can use it in the GTK/C call
    //below.
    //
    auto g_columnviewcolumn = column->gobj();
    assert(g_columnviewcolumn);
    
    //
    //Now that we have the required pointers, we can now invoke the GTK/C
    //call to scroll the columnview to the row ROW and col COL.
    //
    gtk_column_view_scroll_to(
      g_columnview,
      row,
      g_columnviewcolumn,
      (GtkListScrollFlags) (GTK_LIST_SCROLL_FOCUS | GTK_LIST_SCROLL_SELECT),
      nullptr
    );
    

    where:

    • view is a Gtk::ColumnView
    • row is the row to which you wish to scroll to
    • col is the column to which you wish to scroll to
    • both row and col are integers.

    It will be easier when GTKMM reaches 4.12 in my distribution as there is a scroll_to() member function and I will not have to use the gobj() members.