Search code examples
cgtk4

How to scroll to the selected Item on GtkListView?


I want to use the GtkListView widget on my app. I've added some items to it. And, I have set an item as the default selected item of the GtkListView. But the problem is, while running the app, I can't automatically scroll to the default selected item. Can anybody check it please? The demo script is given below ---

#include <gtk/gtk.h>
static void
setup_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data)             
{
  GtkWidget *lb = gtk_label_new (NULL);
  gtk_list_item_set_child (listitem, lb);
}

static void
bind_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) {
  GtkWidget *lb = gtk_list_item_get_child (listitem);
  GtkStringObject *strobj = gtk_list_item_get_item (listitem);
  gtk_label_set_text (GTK_LABEL (lb), gtk_string_object_get_string (strobj));
}

static void
scroll_set(GtkListView *listview, gpointer user_data)
{
  GtkScrollInfo *scrinfo = gtk_scroll_info_new ();
  gtk_scroll_info_set_enable_vertical ((GtkScrollInfo *)scrinfo, TRUE);
  gtk_list_view_scroll_to ((GtkListView *)listview, 10, GTK_LIST_SCROLL_FOCUS, scrinfo); //avilable 4.12
}


static void
app_activate (GApplication *application) {
  GtkApplication *app = GTK_APPLICATION (application);
  GtkWidget *win = gtk_application_window_new (app);
  gtk_window_set_default_size (GTK_WINDOW (win), 200, 100);
  GtkWidget *scr = gtk_scrolled_window_new ();
  gtk_window_set_child (GTK_WINDOW (win), scr);

  char *array[] = {
    "one", "two", "three", "four", "five" ,"six"," seven", 
    "eight" ,"nine", "ten", "eleven", "twelve", "thirteen",NULL
  };

  GtkWidget *lv = gtk_list_view_new(NULL,NULL); 

  GtkStringList *sl =  gtk_string_list_new ((const char * const *) array);
  GtkSingleSelection *selection = gtk_single_selection_new(G_LIST_MODEL(sl));
  gtk_single_selection_set_autoselect(selection, TRUE);
  gtk_list_view_set_model(GTK_LIST_VIEW(lv),GTK_SELECTION_MODEL(selection));

  GtkListItemFactory *factory = gtk_signal_list_item_factory_new ();
  g_signal_connect (factory, "setup", G_CALLBACK (setup_cb), NULL);
  g_signal_connect (factory, "bind", G_CALLBACK (bind_cb), NULL);

  gtk_list_view_set_factory (GTK_LIST_VIEW (lv),factory);
  gtk_single_selection_set_selected ((GtkSingleSelection *)selection, 10);

  gtk_widget_set_visible(lv, TRUE);
  /*scroll_set cb not working*/
  g_signal_connect (lv, "show", G_CALLBACK (scroll_set), NULL);

  gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), lv);
  gtk_window_present (GTK_WINDOW (win));
}


int
main (int argc, char **argv) {
  GtkApplication *app;
  int stat;
  app = gtk_application_new ("com.gtk.listview.test", G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
  stat =g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);
 return stat;
}

Note: Here item number 10 is set as selected item. While running the app, to the item number 10 is not scrolling.


Solution

  • Here is slightly changed application in Python:

    import gi
    gi.require_version('Gtk', '4.0')
    from gi.repository import Gtk
    
    def on_list_item_factory_setup(signal_list_item_factory, objekt):
        objekt.set_child(Gtk.Label())
    
    def on_list_item_factory_bind(signal_list_item_factory, objekt):
        objekt.get_child().set_label(objekt.get_item().get_string())
    
    def on_selection_model_selection_changed(selection_model, position, n_items, user_data):
        list_view = user_data
        scroll_info = Gtk.ScrollInfo.new()
        scroll_info.set_enable_vertical(True)
        list_view.scroll_to(selection_model.get_selected(), Gtk.ListScrollFlags.FOCUS, scroll_info)
    
    def on_spin_button_value_changed(spin_button, user_data):
        selection_model = user_data
        selection_model.set_selected(spin_button.get_value_as_int() - 1)
    
    def on_application_activate(app):
        arr = [
            'one', 'two', 'three', 'four', 'five' ,'six',' seven', 
            'eight' ,'nine', 'ten', 'eleven', 'twelve', 'thirteen'
        ]
    
        window = Gtk.ApplicationWindow(application=app)
        window.set_default_size(200, 100)
    
        header_bar = Gtk.HeaderBar.new()
        window.set_titlebar(header_bar)
    
        adjustment = Gtk.Adjustment.new(1, 1, len(arr), 1, 1, 1)
        spin_button = Gtk.SpinButton.new(adjustment, 0, 0)
        header_bar.pack_end(spin_button)
    
        scrolled = Gtk.ScrolledWindow.new()
        window.set_child(scrolled)
    
        list_model = Gtk.StringList.new(arr)
        selection_model = Gtk.SingleSelection.new(list_model)
        selection_model.set_autoselect(True)
        spin_button.connect('value_changed', on_spin_button_value_changed, selection_model)
    
        list_item_factory = Gtk.SignalListItemFactory.new()
        list_item_factory.connect('setup', on_list_item_factory_setup)
        list_item_factory.connect('bind', on_list_item_factory_bind)
    
        list_view = Gtk.ListView.new(selection_model, list_item_factory)
        selection_model.connect('selection-changed', on_selection_model_selection_changed, list_view)
    
        scrolled.set_child(list_view)
    
        window.present()
    
    
    if __name__ == '__main__':
        import sys
    
        app = Gtk.Application(application_id='com.example.ScrollTestApp')
        app.connect('activate', on_application_activate)
    
        app.run(sys.argv)
    

    and its C version:

    #include <gtk/gtk.h>
    
    static void
    on_list_item_factory_setup (GtkSignalListItemFactory *item_factory, GObject* objekt, gpointer user_data)
    {
      GtkListItem *list_item = GTK_LIST_ITEM (objekt);
      GtkWidget *label = gtk_label_new (NULL);
      gtk_list_item_set_child (list_item, label);
    }
    
    static void
    on_list_item_factory_bind (GtkSignalListItemFactory *item_factory, GObject *objekt, gpointer user_data)
    {
      GtkListItem *list_item = GTK_LIST_ITEM (objekt);
      GtkWidget *label = gtk_list_item_get_child (list_item);
      GtkStringObject *string_object = gtk_list_item_get_item (list_item);
      gtk_label_set_text (GTK_LABEL (label), gtk_string_object_get_string (string_object));
    }
    
    static void
    on_selection_model_selection_changed (GtkSelectionModel* selection_model, guint position, guint n_items, gpointer user_data)
    {
      GtkListView *list_view = GTK_LIST_VIEW (user_data);
      GtkScrollInfo *scroll_info = gtk_scroll_info_new ();
      gtk_scroll_info_set_enable_vertical (scroll_info, TRUE);
      int selected = gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (selection_model));
      gtk_list_view_scroll_to (list_view, selected, GTK_LIST_SCROLL_FOCUS, scroll_info);
    }
    
    static void
    on_spin_button_value_changed (GtkSpinButton *spin_button, gpointer user_data)
    {
      GtkSingleSelection *selection_model = user_data;
      int pos = gtk_spin_button_get_value_as_int(spin_button) - 1;
      gtk_single_selection_set_selected (selection_model, pos);
    }
    
    static void
    on_application_activate (GApplication *application, gpointer user_data)
    {
      char *array[] = {
        "one", "two", "three", "four", "five" , "six", "seven", 
        "eight" ,"nine", "ten", "eleven", "twelve", "thirteen",
        NULL
      };
    
      GtkWidget *window = gtk_application_window_new (GTK_APPLICATION (application));
      gtk_window_set_default_size (GTK_WINDOW (window), 200, 100);
    
      GtkWidget *header_bar = gtk_header_bar_new ();
      gtk_window_set_titlebar (GTK_WINDOW (window), header_bar);
    
      GtkAdjustment *adjustment = gtk_adjustment_new (1, 1, G_N_ELEMENTS (array), 1, 1, 1);
      GtkWidget *spin_button = gtk_spin_button_new(adjustment, 0, 0);
      gtk_header_bar_pack_end (GTK_HEADER_BAR (header_bar), spin_button);
    
      GtkWidget *scrolled = gtk_scrolled_window_new ();
      gtk_window_set_child (GTK_WINDOW (window), scrolled);
    
      GtkStringList *list_model = gtk_string_list_new ((const char * const *) array);
      GtkSingleSelection *selection_model = gtk_single_selection_new (G_LIST_MODEL (list_model));
      gtk_single_selection_set_autoselect(selection_model, TRUE);
      g_signal_connect(spin_button, "value_changed", G_CALLBACK (on_spin_button_value_changed), selection_model);
    
      GtkListItemFactory *list_item_factory = gtk_signal_list_item_factory_new ();
      g_signal_connect (list_item_factory, "setup", G_CALLBACK (on_list_item_factory_setup), NULL);
      g_signal_connect (list_item_factory, "bind", G_CALLBACK (on_list_item_factory_bind), NULL);
    
      GtkWidget *list_view = gtk_list_view_new (GTK_SELECTION_MODEL (selection_model), list_item_factory); 
      g_signal_connect(selection_model, "selection-changed", G_CALLBACK (on_selection_model_selection_changed), list_view);
    
      gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled), list_view);
      gtk_window_present (GTK_WINDOW (window));
    }
    
    
    int
    main (int argc, char **argv)
    {
      GtkApplication *application = gtk_application_new ("com.example.ScrollTestApp", G_APPLICATION_DEFAULT_FLAGS);
      g_signal_connect (application, "activate", G_CALLBACK (on_application_activate), NULL);
      int stat = g_application_run (G_APPLICATION (application), argc, argv);
      g_object_unref (application);
      return stat;
    }
    

    I don't know why on application initialization, some signals not emitted. Needs a GTK expert here to answer. But as you see, the signal system works correctly afterwards.

    As you see, for small testing purposes, prototyping in Python, Javascript, even in Vala may help.

    EDIT:

    In comments, I wrote about early setting the selected row and scrolling to selected row, while GTK does not know widget sizes. Here I added a simple loop after gtk_window_present to ask Gtk to complete all its event handling operations (e.g. calculating sizes, handling input events, drawings, ...), then set the row. It works as intended and list view scrolled correctly and shows selected row:

    #include <gtk/gtk.h>
    
    static void
    on_list_item_factory_setup (GtkSignalListItemFactory *item_factory, GObject* objekt, gpointer user_data)
    {
      GtkListItem *list_item = GTK_LIST_ITEM (objekt);
      GtkWidget *label = gtk_label_new (NULL);
      gtk_list_item_set_child (list_item, label);
    }
    
    static void
    on_list_item_factory_bind (GtkSignalListItemFactory *item_factory, GObject *objekt, gpointer user_data)
    {
      GtkListItem *list_item = GTK_LIST_ITEM (objekt);
      GtkWidget *label = gtk_list_item_get_child (list_item);
      GtkStringObject *string_object = gtk_list_item_get_item (list_item);
      gtk_label_set_text (GTK_LABEL (label), gtk_string_object_get_string (string_object));
    }
    
    static void
    on_selection_model_selection_changed (GtkSelectionModel* selection_model, guint position, guint n_items, gpointer user_data)
    {
      GtkListView *list_view = GTK_LIST_VIEW (user_data);
      GtkScrollInfo *scroll_info = gtk_scroll_info_new ();
      gtk_scroll_info_set_enable_vertical (scroll_info, TRUE);
      int selected = gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (selection_model));
      gtk_list_view_scroll_to (list_view, selected, GTK_LIST_SCROLL_FOCUS, scroll_info);
    }
    
    static void
    on_application_activate (GApplication *application, gpointer user_data)
    {
      char *array[] = {
        "one", "two", "three", "four", "five" , "six", "seven", 
        "eight" ,"nine", "ten", "eleven", "twelve", "thirteen",
        NULL
      };
    
      GtkWidget *window = gtk_application_window_new (GTK_APPLICATION (application));
      gtk_window_set_default_size (GTK_WINDOW (window), 200, 100);
    
      GtkWidget *scrolled = gtk_scrolled_window_new ();
      gtk_window_set_child (GTK_WINDOW (window), scrolled);
    
      GtkStringList *list_model = gtk_string_list_new ((const char * const *) array);
      GtkSingleSelection *selection_model = gtk_single_selection_new (G_LIST_MODEL (list_model));
      gtk_single_selection_set_autoselect(selection_model, TRUE);
    
      GtkListItemFactory *list_item_factory = gtk_signal_list_item_factory_new ();
      g_signal_connect (list_item_factory, "setup", G_CALLBACK (on_list_item_factory_setup), NULL);
      g_signal_connect (list_item_factory, "bind", G_CALLBACK (on_list_item_factory_bind), NULL);
    
      GtkWidget *list_view = gtk_list_view_new (GTK_SELECTION_MODEL (selection_model), list_item_factory); 
      g_signal_connect(selection_model, "selection-changed", G_CALLBACK (on_selection_model_selection_changed), list_view);
    
      gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled), list_view);
    
      gtk_window_present (GTK_WINDOW (window));
    
      while (g_main_context_pending (g_main_context_default ()))
        g_main_context_iteration (NULL, TRUE);
    
      gtk_single_selection_set_selected (selection_model, 10);
    }
    
    
    int
    main (int argc, char **argv)
    {
      GtkApplication *application = gtk_application_new ("com.example.ScrollTestApp", G_APPLICATION_DEFAULT_FLAGS);
      g_signal_connect (application, "activate", G_CALLBACK (on_application_activate), NULL);
      int stat = g_application_run (G_APPLICATION (application), argc, argv);
      g_object_unref (application);
      return stat;
    }
    

    Gtk and also its object system, GObject, GType, ... are amazing. I strongly recommend every C developer to study its internals and codes. If you develop Gtk based widgets, libraries, ..., you should learn its internals.