How navigation currently works in FX, and an enhancement proposal

John Hendrikx john.hendrikx at gmail.com
Thu Sep 19 15:38:14 UTC 2024


I've been looking into how exactly navigation keys are being used in FX, 
and who is responsible for handling them:

- Controls can choose to install navigational keys directly in their 
input map (using FocusTraversalInputMap::getFocusTraversalMappings)
- Controls can choose to do nothing and leave navigation keys to bubble 
up to Scene, at which point Scene will act on any unconsumed navigation 
keys (in the same was as the traversal mappings would)

Scene basically is capable of almost all navigation you could possibly 
want out of the box.  Any control that does not install navigation keys, 
and leaves said keys to bubble up gets navigation for **free**.  This is 
almost all controls in JavaFX, and it makes sense as Controls should not 
care about navigation, they should only care about key presses that 
affect them directly.  Navigation should be a concern somewhere higher 
up in the hierarchy.

So why do some controls install their own navigation keys?

There are two answers:

1. For some controls, navigation is conditional. A Spinner only allows 
directional navigation for the left/right keys, or up/down keys 
depending on its orientation.
2. There is an unfortunate choice in ScrollPane that consumes 
directional keys for scrolling purposes, and so if such keys were left 
to bubble up, they would not end up at Scene.  Any control supporting 
directional navigation therefore must **specifically** install these 
bindings directly, even though navigation is not their concern (a Button 
cares about being pressed, not about activating unrelated controls nearby).

The ScrollPane eating directional keys is an odd choice. In order for it 
to do so one of the following must be true:

- A control inside it has focus that should act on directional 
navigation, but forgot to install navigation bindings (a custom 
control).  Such a control would work perfectly when not part of a 
ScrollPane (as Scene would then handle directional navigation), but 
break when placed inside it.  Note that all JavaFX controls do this 
"properly".  I couldn't find any controls that would leave directional 
keys to bubble up for a ScrollPane to consume.

- The ScrollPane itself has focus; this can only happen when directly 
selected with the mouse (or focus traversable is set to true) and no 
specific control inside the pane was selected.  The ScrollPane receives 
the ":focused" style, clearly indicating that it is the target for 
keyboard events to the user.

In short, ScrollPane is making navigation a lot more complex within FX 
than it needs to be.  Especially custom controls that do not have access 
(currently) to install navigational bindings will suffer from this, and 
will have to resort to their own navigation implementation for 
directional keys when placed inside a ScrollPane.

# Proposal

I think ScrollPane violates what I think should be a fundamental rule.  
Keys should only be consumed by what the user perceives as the focused 
control (ie. the one outlined with a highlighted border), with the only 
exceptions being short cuts (from a menu) or mnemonics.  Containers such 
controls happen to be placed in should NOT consume key events -- the 
container is not the control with the focus, and so would confuse the 
user.  Only ScrollPane is violating this currently.  Note that if the 
ScrollPane has focus itself (and it has the :focused highlight) then it 
is perfectly fine and expected for it to consume keys as much as it wants.

This is why I think we should modify ScrollPane to not consume the 
directional keys, unless it specifically has the focus.  All other 
controls can then remove their navigational bindings and leave them to 
bubble up to Scene, cleaning up their behaviors so they can focus on 
other concerns.  Custom controls would no longer need to install 
navigational bindings either, and would not need to worry about being 
placed inside a ScrollPane and having their directional navigation broken.

Optional, but recommended, controls like Spinner should only act on the 
directional keys intended for them, and leave the ones they can't use to 
bubble up.  So a vertical spinner would consume up/down for changing the 
spinner value, but would leave left/right untouched for Scene to 
handle.  Controls that install a full set of navigational keys (like 
Button, ListView and TitledPane) don't need to do so anymore.

I think I will file a ticket for this soon, but I'm curious what others 
think of this analysis.

Note that by solving this problem, the need to make navigation 
functionality available to custom controls severely diminishes as one 
can simple leave the KeyEvents responsible for standard navigation to 
bubble up (recommended as this may be different for each platform).

--John



More information about the openjfx-dev mailing list