yktoo
yktoo

Reputation: 2986

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:

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

Upvotes: 5

Views: 1501

Answers (2)

yktoo
yktoo

Reputation: 2986

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.

Upvotes: 4

Emmanuel Touzery
Emmanuel Touzery

Reputation: 9183

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.

Upvotes: 3

Related Questions