Search code examples
gogtkgtk3

Scroll to selected row in GtkListBox


I'm a bit out of ideas here. I want a very simple thing: to be able to select a given GtkListBox row programmatically and then scroll the list box (which is wrapped in a ScrolledWindow and a Viewport).

Selecting a row is trivial (my code is Go & gotk3, but that's not so important):

listBox.SelectRow(row)

But scrolling to the row proved to be a real challenge. Whatever I tried, I failed:

  • I tried to focus the row, but it helped nothing
  • I tried to figure out the row's Y-coordinate using gtk_widget_translate_coordinates(), but it returns -1 for any row
  • Perhaps I can find out which row is at the top and the bottom of the list box and use that to scroll the ScrolledWindow but I can't figure out how to do that.

Update: I've tried what's proposed here: Manually scroll to a child in a Gtk.ScrolledWindow, but it didn't work as still no scrolling occurred:

listbox.SelectRow(rowToSelect)
listbox.SetFocusVAdjustment(listbox.GetAdjustment())
if rowToSelect != nil {
    rowToSelect.GrabFocus()
}

I also tried the same with rowToSelect's child using the code below, to no avail:

if c, err := rowToSelect.GetChild(); err == nil {
    c.GrabFocus()
}


Solution

  • I've finally nailed it thanks to the hint by Emmanuel Touzery. I didn't have to go as far as to use timers, but the problem was indeed that at the moment of filling of the list box the row hasn't been realised yet so no coordinate translation could possibly happen.

    What I did is scheduled the scrolling using GLib's idle_add(), which makes it happen later downstream, and that seemed to have worked perfectly: see this commit for details.

    In short, it all boils down to the following code:

    func ListBoxScrollToSelected(listBox *gtk.ListBox) {
        // If there's selection
        if row := listBox.GetSelectedRow(); row != nil {
            // Convert the row's Y coordinate into the list box's coordinate
            if _, y, _ := row.TranslateCoordinates(listBox, 0, 0); y >= 0 {
                // Scroll the vertical adjustment to center the row in the viewport
                if adj := listBox.GetAdjustment(); adj != nil {
                    _, rowHeight := row.GetPreferredHeight()
                    adj.SetValue(float64(y) - (adj.GetPageSize()-float64(rowHeight))/2)
                }
            }
        }
    }
    

    The above function has to be called using the glib.IdleAdd() and not in the code that fills the list box.