【问题标题】:JavaFX: scrolling vs. focus traversal with arrow keysJavaFX:使用箭头键滚动与焦点遍历
【发布时间】:2013-10-28 00:02:14
【问题描述】:

我有一个包含可聚焦节点的ScrollPane

当前的默认行为是:

  • Shift + , , , 移动焦点

  • ,,,滚动视图

    李>

我想要反过来。 我怎样才能做到这一点或者我应该从哪里开始?


[EDIT]好吧,还有另一种脆弱的方法。

与其乱搞事件,不如乱搞KeyBindings。

    scrollPane.skinProperty().addListener(new ChangeListener<Skin<?>>() {
        @Override
        public void changed(ObservableValue<? extends Skin<?>> observable, Skin<?> oldValue, Skin<?> newValue) {
            ScrollPaneSkin scrollPaneSkin = (ScrollPaneSkin) scrollPane.getSkin();
            ScrollPaneBehavior scrollPaneBehavior = scrollPaneSkin.getBehavior();
            try {
                Field keyBindingsField = BehaviorBase.class.getDeclaredField("keyBindings");
                keyBindingsField.setAccessible(true);
                List<KeyBinding> keyBindings = (List<KeyBinding>) keyBindingsField.get(scrollPaneBehavior);
                List<KeyBinding> newKeyBindings = new ArrayList<>();
                for (KeyBinding keyBinding : keyBindings) {
                    KeyCode code = keyBinding.getCode();
                    newKeyBindings.add(code == KeyCode.LEFT || code == KeyCode.RIGHT || code == KeyCode.UP || code == KeyCode.DOWN ? keyBinding.shift() : keyBinding);
                }
                keyBindingsField.set(scrollPaneBehavior, newKeyBindings);
            } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
                LOGGER.warn("private api changed.", e);
            }
        }
    });

我认为,如果 KeyBindings 更加非静态、可修改和公开,那可能是更简洁的方式。

【问题讨论】:

    标签: focus javafx scrollview key-bindings javafx-8


    【解决方案1】:

    使用event filter 捕获相关的关键事件,并在事件开始冒泡之前将它们重新映射到不同的关键事件。

    重新映射默认键是一件棘手的事情:

    1. 可能会使用户感到困惑。
    2. 可能会产生意想不到的副作用(例如,TextFields 可能不再像您预期的那样工作)。

    所以要小心使用:

    import javafx.application.*;
    import javafx.event.*;
    import javafx.scene.Scene;
    import javafx.scene.control.*;
    import javafx.scene.input.KeyEvent;
    import javafx.scene.layout.TilePane;
    import javafx.stage.Stage;
    
    import java.util.*;
    
    public class ScrollInterceptor extends Application {
    
      @Override
      public void start(Stage stage) {
        ScrollPane scrollPane = new ScrollPane(
          createScrollableContent()
        );
    
        Scene scene = new Scene(
          scrollPane,
          300, 200
        );
    
        remapArrowKeys(scrollPane);
    
        stage.setScene(scene);
        stage.show();
    
        hackToScrollToTopLeftCorner(scrollPane);
      }
    
      private void remapArrowKeys(ScrollPane scrollPane) {
        List<KeyEvent> mappedEvents = new ArrayList<>();
        scrollPane.addEventFilter(KeyEvent.ANY, new EventHandler<KeyEvent>() {
          @Override
          public void handle(KeyEvent event) {
            if (mappedEvents.remove(event))
              return;
    
            switch (event.getCode()) {
              case UP:
              case DOWN:
              case LEFT:
              case RIGHT:
                KeyEvent newEvent = remap(event);
                mappedEvents.add(newEvent);
                event.consume();
                Event.fireEvent(event.getTarget(), newEvent);
            }
          }
    
          private KeyEvent remap(KeyEvent event) {
            KeyEvent newEvent = new KeyEvent(
                event.getEventType(),
                event.getCharacter(),
                event.getText(),
                event.getCode(),
                !event.isShiftDown(),
                event.isControlDown(),
                event.isAltDown(),
                event.isMetaDown()
            );
    
            return newEvent.copyFor(event.getSource(), event.getTarget());
          }
        });
      }
    
      private TilePane createScrollableContent() {
        TilePane tiles = new TilePane();
        tiles.setPrefColumns(10);
        tiles.setHgap(5);
        tiles.setVgap(5);
        for (int i = 0; i < 100; i++) {
          Button button = new Button(i + "");
          button.setMaxWidth(Double.MAX_VALUE);
          button.setMaxHeight(Double.MAX_VALUE);
          tiles.getChildren().add(button);
        }
        return tiles;
      }
    
      private void hackToScrollToTopLeftCorner(final ScrollPane scrollPane) {
        Platform.runLater(new Runnable() {
          @Override
          public void run() {
            scrollPane.setHvalue(scrollPane.getHmin());
            scrollPane.setVvalue(0);
          }
        });
      }
    
      public static void main(String[] args) {
        launch(args);
      }
    }
    

    【讨论】:

    • 这正是我想要的。快速第一次尝试将代码接管到我的应用程序中,但结果是StackOverflowError。我会在晚上看这个。谢谢!
    • copyFor 语句是关键,否则代码 JavaFX 系统将在内部复制事件以设置源和目标,因此您将陷入无限循环的事件重新映射,打开和关闭 shift 修饰符,导致溢出。我的测试系统是带有 Java 8 的 OS X。
    • 不知何故,在我的应用程序中,没有事件从mappedEvents 列表中删除。你的例子工作正常。到目前为止,我无法弄清楚有什么不同。
    • 也许attach the source 并使用调试器逐步完成(这就是我决定使用 copyFor 的方式)。我希望解决方案不是那么脆弱。或者,您可以创建自己的控件behaviours 和皮肤来覆盖默认处理,但这是一些工作。
    • 我为我的问题添加了另一种方法。
    猜你喜欢
    • 2020-02-20
    • 1970-01-01
    • 2018-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多