silvalli
silvalli

Reputation: 315

How to override ScrollPane’s default arrow key behavior

I want arrow keys in a ScrollPane to work in a way other than the default. Consuming arrow keys in a KeyPressed handler on the ScrollPane does not prevent them from being processed by he ScrollPane. Is there something that works?

In the example program,

Start

Fig 1. Start

Start, then h key

Fig 2. Start, then h key (rect moves left on the grid)

Start, then left-arrow key

Fig. 3. Start, then left-arrow key (rect moves left on the grid, and the ScrollPane slider makes an unwanted move to the left)

Notes:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class ScrollPaneArrowKeys extends Application {

  public static void main(String[] args) { launch(args); }

  static Rectangle  rect;
  static ScrollPane sp;

  @Override
  public void start(Stage stage) {
        rect  = new Rectangle (100, 50);
    var pane  = new Pane      (rect);
        sp    = new ScrollPane(pane);
    var scene = new Scene     (sp);
    rect.setFill(Color.PALEGREEN);
    pane.setMinWidth (600);
    pane.setMinHeight(150);
    pane.setStyle(grid);
    sp.setMinWidth (300);
    sp.setMinHeight(170);
    sp.setHvalue(0.5);
    sp.setOnKeyPressed(ScrollPaneArrowKeys::onKeyPressed);
    sp.requestFocus();
    reset();
    stage.setScene(scene);
    stage.show();
  }

  private static void onKeyPressed(KeyEvent e) {
    switch (e.getCode()) {
      case H:
      case LEFT:   rect.setX(rect.getX() - 10); break;
      case L:
      case RIGHT:  rect.setX(rect.getX() + 10); break;
      case ESCAPE: reset();                     break;
      default:                                  break;
    }
    e.consume();
  }

  private static void reset() {
    rect.setX(250);
    rect.setY( 50);
    sp.setHvalue(0.5);
  }

  String grid = """
      -fx-background-color: white,
      linear-gradient(from 0px 0px to 10px  0px, repeat, #d8f0f8 6.25%, transparent 6.25%),
      linear-gradient(from 0px 0px to 50px  0px, repeat, #b0e0e8 1.25%, transparent 1.25%),
      linear-gradient(from 0px 0px to  0px 10px, repeat, #d8f0f8 6.25%, transparent 6.25%),
      linear-gradient(from 0px 0px to  0px 50px, repeat, #b0e0e8 1.25%, transparent 1.25%);
      """;
}

Upvotes: 0

Views: 293

Answers (1)

anko
anko

Reputation: 1708

If you want to override the default key handling of the Scroll Pane, you can achieve it with registering an Event Filter (instead of adding another Event Handler). You can read about this in e. g. this posting: JavaFX: What is the difference between EventHandler and EventFilter?

Considering the User Experience you may want to add another possibility for scrolling (e. g. CTRL + LEFT etc.). Here is a working example (the most part is taken from your code):

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;


public class ScrollPaneArrowKeys extends Application {

    private final Rectangle rect = new Rectangle(100, 50);
    private final Pane pane = new Pane(rect);
    private final ScrollPane sp = new ScrollPane(pane);
    private final Scene scene = new Scene(sp);

    @Override
    public void start(Stage stage) {

        // Register an Event Filter to override the default behaviour:
        sp.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
            e.consume(); // prevent default key handling
            // Allow the user to scroll horizontally with the control key:
            if (e.isControlDown())
                switch (e.getCode()) {
                    case LEFT -> sp.setHvalue(sp.getHvalue() - .1);
                    case RIGHT -> sp.setHvalue(sp.getHvalue() + .1);
                }
            else
                // Move they rectangle only:
                switch (e.getCode()) {
                    case H, LEFT -> rect.setX(rect.getX() - 10);
                    case L, RIGHT -> rect.setX(rect.getX() + 10);
                    case ESCAPE -> resetRectPosition();
                }
        });

        initStyling();
        resetRectPosition();
        stage.setScene(scene);
        stage.show();
        sp.requestFocus();
    }

    private void initStyling() {
        rect.setFill(Color.PALEGREEN);

        pane.setMinWidth(600);
        pane.setMinHeight(150);
        pane.setStyle("""
                -fx-background-color: white,
                linear-gradient(from 0px 0px to 10px  0px, repeat, #d8f0f8 6.25%, transparent 6.25%),
                linear-gradient(from 0px 0px to 50px  0px, repeat, #b0e0e8 1.25%, transparent 1.25%),
                linear-gradient(from 0px 0px to  0px 10px, repeat, #d8f0f8 6.25%, transparent 6.25%),
                linear-gradient(from 0px 0px to  0px 50px, repeat, #b0e0e8 1.25%, transparent 1.25%);
                """);

        sp.setMinWidth(300);
        sp.setMinHeight(170);
        sp.setHvalue(0.5);
    }

    private void resetRectPosition() {
        rect.setX(250);
        rect.setY(50);
        sp.setHvalue(0.5);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Upvotes: 2

Related Questions