Summary
ScrollPane
will no longer act on keys to scroll its view port when it does not have the focus. Before this change, ScrollPane
would consume keys that bubbled up from focused child controls, even though it did not have the focus. The behavior when the ScrollPane
has direct focus is unchanged.
Problem
Users generally expect that the focused control is the one that will respond to key presses. A visual cue for this is present in the form of a highlight to indicate this. Users will also expect that controls will act similar regardless of their placement (within a ScrollPane
or not). However, as ScrollPane
would act on bubbled up keys without checking whether it had focus, pressing keys that users think are intended for the focused control may trigger an action in the surrounding scroll pane instead.
This is especially apparent when designing a custom control. The custom control will function as any other JavaFX control, participating in both logical (tab) and directional navigation (arrow keys) as any FX control would. However, when such a control is placed within a ScrollPane
this breaks down, and directional navigation is blocked by the scroll pane consuming these keys for scrolling the viewport.
The standard FX controls are (mostly) unaffected by this, as they all explicitly handle most keys that ScrollPane
acts on. For example, a Button
installs navigation handlers for the arrow keys. It however would not have to, and should not have to, if it could let those keys bubble up safely to Scene
where navigation concerns are supposed to be handled.
Another indication that the current behavior is not that useful is that no FX controls that incorporate a scrollable area will delegate their default scrolling behavior to ScrollPane
. This is because it is not aware of its actual content, and any scrolling that it provides would be unaware of page sizes, rows or columns, or any other unit that would make sense as good scroll unit.
This change will open the way to removing navigational concerns from controls, and leaving this to Scene
. As controls will no longer need to explicitly act on directional navigation keys, these can be left to bubble up, simplifying their implementations and also allows users to change the meaning of such keys more easily as controls no longer have to consume them early.
Solution
Modify the internal ScrollPaneBehavior
to take focus into account before consuming key presses.
For users that relied on the old behavior, installing an event handler on ScrollPane
to handle the keys even when not focused is offered as a solution. For example:
scrollPane.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
double x = 0;
double y = 0;
switch(e.getCode()) {
case KeyCode.LEFT -> x = -0.1;
case KeyCode.RIGHT -> x = 0.1;
case KeyCode.UP -> y = -0.1;
case KeyCode.DOWN -> y = 0.1;
case KeyCode.PAGE_UP -> y = -0.9;
case KeyCode.PAGE_DOWN -> y = 0.9;
case KeyCode.SPACE -> y = 0.9;
case KeyCode.HOME -> x = y = Double.NEGATIVE_INFINITY;
case KeyCode.END -> x = y = Double.POSITIVE_INFINITY;
default -> {}
}
if(x != 0 || y != 0) {
scrollByFraction(scrollPane, x, y);
e.consume();
}
});
Which makes use of this helper function:
static void scrollByFraction(ScrollPane scrollPane, double x, double y) {
Node content = scrollPane.getContent();
if (content == null) {
return;
}
Bounds viewportBounds = scrollPane.getViewportBounds();
Bounds layoutBounds = content.getLayoutBounds();
if (x != 0) {
double visibleFraction = viewportBounds.getWidth() / layoutBounds.getWidth();
double range = scrollPane.getHmax() - scrollPane.getHmin();
double scrollFactor = range * visibleFraction / (1 - visibleFraction);
scrollPane.setHvalue(scrollPane.getHvalue() + x * scrollFactor);
}
if (y != 0) {
double visibleFraction = viewportBounds.getHeight() / layoutBounds.getHeight();
double range = scrollPane.getVmax() - scrollPane.getVmin();
double scrollFactor = range * visibleFraction / (1 - visibleFraction);
scrollPane.setVvalue(scrollPane.getVvalue() + y * scrollFactor);
}
}
Specification
A paragraph is added to the ScrollPane
documentation:
ScrollPane only acts on key presses when it has the focus ({@link #isFocused()} returns {@code true}) and won't respond to
unconsumed key events that bubble up from a focused child control.
- csr of
-
JDK-8340852 ScrollPane should not consume navigation keys when it doesn't have direct focus
-
- Resolved
-