6

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

yktoo
  • 2,696
  • 1
  • 23
  • 35
  • Does this answer your question? [Manually scroll to a child in a Gtk.ScrolledWindow](https://stackoverflow.com/questions/54007196/manually-scroll-to-a-child-in-a-gtk-scrolledwindow) – nielsdg Jun 19 '20 at 07:55
  • Not really. I've seen that answer but it didn't make a lot of sense to me. At least, I've tried what's described there and it didn't work. Perhaps I've implemented it wrong. – yktoo Jun 19 '20 at 08:29
  • What was your try? Looks like you should combine `gtk_list_box_get_adjustment` and `gtk_container_set_focus_hadjustment` – Alexander Dmitriev Jun 19 '20 at 13:55
  • @AlexanderDmitriev updated the question with an explanation why it wouldn't work. – yktoo Jun 21 '20 at 07:56
  • 1
    I have the same issue. Asked in the [gnome discourse](https://discourse.gnome.org/t/listbox-programmatically-scroll-to-row/3844), no answer so far. – Emmanuel Touzery Jul 22 '20 at 04:47

2 Answers2

5

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.

yktoo
  • 2,696
  • 1
  • 23
  • 35
3

So, I had the same issue but managed to make it work in my case. I think there are good chances my solution will work for you too.

Since the grab_focus method didn't work, I started implementing a workaround solution using listbox_get_row_at_y. Highly unsatisfying, but hopefully it was going to work. And.. it didn't work, because get_row_at_y would always return null, for all the y values I'd feed it. And I knew the listbox wasn't empty. So that made me realize I was trying to focus a row that I had just been adding to the listbox.. The row wasn't realized yet, it couldn't be focused because it wasn't ready for that yet.

So I changed my code to fill the listbox, wait a 100ms timeout, and only then call grab_focus. And that worked!

I'm actually using a library which is wrapping the timeout call for me, but I think you could use g_timeout_add in 'raw' gtk for that purpose.

Note that this means that calling grab_focus on a listbox that was already filled beforehand and the items realized on screen should work directly. If that's your situation then this won't help you.

Emmanuel Touzery
  • 9,008
  • 3
  • 65
  • 81