Improvements for Focus Traversal code
John Hendrikx
john.hendrikx at gmail.com
Tue May 16 11:54:57 UTC 2023
Hi list,
I've gone down another rabbit hole in JavaFX, this time related to focus
traversal.
# Navigation keys are not handled universally for all controls
Some controls install custom behaviors, and some don't. The custom
behaviors often include direct handling of navigation keys
(getFocusTraversalMappings). The controls that don't (often controls
that are not focus traversable by default, like Label) rely on the
SceneEventDispatcher to handle navigation keys.
However, this leads to subtle differences; when placed inside another
control that may be interested in the navigation keys (like ListView,
TableView, ScrollPane) a Button will still respond to all navigation
keys directly (and correctly), while a focus traversable Label will let
these events bubble up, possibly to be consumed by its parent (ie.
pressing DOWN may not focus another control inside the View, but instead
scroll the view down as if the View had focus). This is inconsistent.
==> As any Node can be made focus traversable, I think the handling of
navigation keys should be done by Node itself; basically, after all
event handlers have run for a Node, a final event handler should be
called which checks for unconsumed navigation key events, and triggers
navigation only then. This would make all Nodes work consistently where
navigation is concerned and would remove the need to add focus traversal
key mappings in Behaviors. Button would respond as it always has, while
a focus traversable label would now work exactly as a Button would, even
when nested in a View type parent.
# Translate Navigation Keys to Higher Level Events
When a key is determined to trigger navigation, we should translate this
to a new type of event, for example NavigationEvent (or TraversalEvent).
This Key event that triggered it is consumed, and this new event is
fired. This new event captures the intention to navigate somewhere
(Navigate NEXT, PREVIOUS, UP, DOWN, LEFT, RIGHT) and participates in the
usual filtering/bubbling phases. This is far better than having a
(potentially platform specific) key event with multiple possible
meanings (sometimes it navigates, sometimes it scrolls) when one wants
to deal with navigation issues.
This would solve a number of problems:
- Navigation events can all be handled in the same place, the Scene.
- One can trigger navigation in a nice programmatic way, instead of
simulating a key press, which may be platform specific, one can send out
a NavigationEvent#NEXT event type.
- Influencing the focus traversal mechanism is much easier, as these
events can be listened to, filtered and consumed as needed. Contrast
this with trying to do this with key events which may have different
meanings depending on the state of the control receiving them. Blocking
navigation for example is a matter of consuming a type of Navigation
event before it bubbles up to the Scene.
- It could allow for focus traversal that goes beyond Scenes (ie. focus
another Scene, or a Swing control). Navigation events not consumed by
Scene could trigger other actions. For NEXT/PREVIOUS navigation this
may require a Scene setting that prevents wrapping around to the
first/last control, and perhaps with the help of some further Navigation
event variations (like nagivating to FIRST or LAST).
- A lot of the Accessor code to be able to reach the (private) traverse
methods would not be needed anymore as the event system can reach the
Scene code without needing accessors
--John
More information about the openjfx-dev
mailing list