ListBox: How to maintain component focus when scrolling

Hi all,

We have a ListBox with custom row components. Our custom row component has child buttons that can be focused by keyboard, and tabbing between them (and between rows) works as expected.

However, focus is lost when scrolling the ListBox. This is because ListBoxModel::refreshComponentForRow() is called repeatedly during scrolling, and existing row components are being re-used for different rows.

If, for example, a component in row 1 is focused and you scroll down, the same component is re-used for a different row further down the list. The result is that, while your scrolling, focus appears to jump around, and never lands quite where you expect it on mouse up.

I may be wrong, but as far as I can tell, ListBox and ListBoxModel don’t seem to have an existing mechanism to cope with this.

Am I missing something? Is it up to me to figure out a workaround?

Many thanks,

The important bit is that the custom component is totally stateless.
In the refreshComponentForRow() you are setting the new data the component is re-used for, so you need to reset the focus as well (maybe passing it up to the listbox itself).

I think from a users perspective the focus should never be in an off-screen item of the list anyway.

1 Like

Thanks for the quick reply, Daniel.

I understand that the custom component must be stateless. What I’m puzzled by is that JUCE doesn’t seem to handle remembering focus before refreshing the list components, and then restoring focus afterwards. I think that’s something I’d expect juce::ListBox to take care of for me.

I can remove focus from my custom component in refreshComponentForRow(), but that loses focus altogether rather than helping me to retain focus while scrolling.

I think from a users perspective the focus should never be in an off-screen item of the list anyway.

I’d have to disagree with this since focused controls can be scrolled out of view and still retain focus in native Windows UI (and I’ll assume on Mac too, although I haven’t checked).

And bearing that in mind, it’s another gotcha, because you should be able to focus a button on row 1, then scroll to row 1000 and hit Tab to focus the next button back in row 1. But row 1 no longer exists when you scroll to row 1000, of course, so at that point there is no focused component to Tab from/to!

As I say, unless I’m missing something, the bottom line seems to be that juce::ListBox simply does not fully support keyboard focus in its current form, and it’s up to the dev to find a workaround. And that’s a shame because it renders the new JUCE 6.1 accessibility feature unusable in list boxes atm.

Ok, I think UX is always a bit subjective :slight_smile:

I think it’s tricky to achieve that behaviour… maybe a custom FocusTraverser could help, but I don’t know, never tried that.

Good luck

EDIT: another thought, if it would be fixed in the juce code, a custom Component should simply not be recycled if it has a child with the keyboard focus…

1 Like

I appreciate your responses, Daniel :slight_smile:
A custom FocusTraverser may indeed be necessary in this case, although I’m hoping one of the JUCE devs might weigh in with a simpler alternative (or even better - a fix!).

Edit: @daniel I was just typing the same thing! Making it not re-use components would probably do the trick.