【问题标题】:Navigating out of a TextField in a GridPane using the arrow keys in JavaFX使用 JavaFX 中的箭头键导航出 GridPane 中的 TextField
【发布时间】:2020-07-08 22:49:14
【问题描述】:

我正在使用 JavaFX 库在 Java 中制作一个数独求解程序。该程序包含一个交互式数独板,由GridPane 中的一系列TextFields 组成。董事会看起来像这样:

现在,光标位于最左上角的TextField。如果该字段中包含文本,则用户将能够使用箭头键在文本中移动光标。但是,我希望用户能够使用箭头键导航到不同的TextField。问题是,该字段处于“打字模式”(我不知道官方术语),所以箭头键只会将光标移动到文本中的不同点,否则它会停留在同一个字段中。

这就是我的意思:

假设我画的那条线是光标。现在,如果我单击左箭头键,光标将移动到 1 的左侧,但我希望它移动左侧的 TextField。如果我单击向下箭头键,则不会发生任何事情,因为 1 下方没有可供光标导航的文本,但我希望它移动到下方的 TextField

GridPane 的代码是这样的:

TextField[][] squares = new TextField[9][9];
GridPane grid = new GridPane();
for (int i = 0; i < 9; i++) {
    for (int j = 0; j < 9; j++) {
        squares[i][j] = new TextField();
        squares[i][j].setPrefHeight(8);
        squares[i][j].setPrefWidth(25);
        grid.add(squares[i][j], j, i);
     }
}
grid.setAlignment(Pos.CENTER);

squares 数组供我访问 GridPane 中的单个 TextFields。

关于如何解决此问题的任何建议?

【问题讨论】:

    标签: java user-interface javafx textfield arrow-keys


    【解决方案1】:

    要完全避免集中的TextField 处理箭头键,您需要在KeyEvent 到达所述TextField 之前拦截它。这可以通过将事件 filter 添加到GridPane 并酌情使用该事件来完成。如果您不确定为什么会这样,您可以查看JavaFX: Handling Events 教程。

    然后您可以使用Node#requestFocus() 以编程方式更改焦点节点。

    我还建议设置每个TextFieldprefColumnCount,而不是尝试手动设置首选尺寸。这样,首选尺寸是根据字体大小计算出来的。

    这是一个例子:

    import javafx.application.Application;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.control.TextField;
    import javafx.scene.control.TextFormatter;
    import javafx.scene.input.KeyEvent;
    import javafx.scene.layout.GridPane;
    import javafx.scene.text.Font;
    import javafx.stage.Stage;
    
    public class App extends Application {
    
      private TextField[][] fields;
    
      @Override
      public void start(Stage primaryStage) {
        GridPane grid = new GridPane();
        grid.setHgap(3);
        grid.setVgap(3);
        grid.setPadding(new Insets(5));
        grid.setAlignment(Pos.CENTER);
        grid.addEventFilter(KeyEvent.KEY_PRESSED, this::handleArrowNavigation);
    
        fields = new TextField[9][9];
    
        for (int i = 0; i < 9; i++) {
          for (int j = 0; j < 9; j++) {
            fields[i][j] = createTextField();
            grid.add(fields[i][j], j, i);
          }
        }
    
        primaryStage.setScene(new Scene(grid));
        primaryStage.show();
      }
    
      private void handleArrowNavigation(KeyEvent event) {
        Node source = (Node) event.getSource(); // the GridPane
        Node focused = source.getScene().getFocusOwner();
        if (event.getCode().isArrowKey() && focused.getParent() == source) {
          int row = GridPane.getRowIndex(focused);
          int col = GridPane.getColumnIndex(focused);
          // Switch expressions were standardized in Java 14
          switch (event.getCode()) {
            case LEFT -> fields[row][Math.max(0, col - 1)].requestFocus();
            case RIGHT -> fields[row][Math.min(8, col + 1)].requestFocus();
            case UP -> fields[Math.max(0, row - 1)][col].requestFocus();
            case DOWN -> fields[Math.min(8, row + 1)][col].requestFocus();
          }
          event.consume();
        }
      }
    
      private TextField createTextField() {
        TextField field = new TextField();
        // Rather than setting the pref sizes manually this will
        // compute the pref sizes based on the font size.
        field.setPrefColumnCount(1);
        field.setFont(Font.font(20));
        field.setTextFormatter(
            new TextFormatter<>(
                change -> {
                  // Only allow the text to be empty or a single digit between 1-9
                  if (change.getControlNewText().matches("[1-9]?")) {
                    // Without this the text goes "off screen" to the left. This also
                    // seems to have the added benefit of selecting the just-entered
                    // text, which makes replacing it a simple matter of typing another
                    // digit.
                    change.setCaretPosition(0);
                    return change;
                  }
                  return null;
                }));
        return field;
      }
    }
    

    上面还为每个 TextField 添加了一个 TextFormatter 以显示将文本限制为 1 到 9 之间的数字的方法。请注意,箭头导航在到达行尾时不会“环绕”或柱子。如果需要,您当然可以修改代码来实现这一点。

    您可能需要考虑为游戏创建模型。这样,业务逻辑就不会直接绑定到 JavaFX UI 对象。当您更新模型时,它会通知视图(可能通过“视图模型”,具体取决于架构),并且视图会相应地自行更新。

    【讨论】:

    • 这很完美,但我无法使用那些嵌入在 switch 语句中的 lambda 表达式。我正在使用 Java 14,所以我很好奇为什么它不起作用。无论哪种方式,我都将其重写为标准 switch 语句,并且它按预期运行。
    • 很高兴为您提供帮助。至于您的错误,我只能建议您仔细检查您的项目是否使用 Java 14 以及您的 IDE 是否配置正确(例如,在 IntelliJ 中确保“项目语言级别”设置为 Java 14)。
    • 如何使用 FXML 创建这个 GridPane?没有办法将 Java for 循环放在 XML 文件中,是吗?
    • 不,FXML 格式不支持循环结构。当某些事情过度重复或动态时,您应该在 FXML 控制器中实现该部分。在您的情况下,最好在 initialize 方法中添加字段。
    • 您知道我在哪里可以找到有关为游戏构建模型的在线信息吗?我真的不明白它是如何工作的。
    【解决方案2】:

    您需要将事件处理程序设置为在箭头键按下时移动,如下所示查看setTextHandler 函数没有错误处理我刚刚写了这个让您了解您应该做什么当您从那里创建 TextFields 时从循环中调用它,它会检查箭头键按下,然后从下一个 TextField 开始 .requestFocus()

    public class Main extends Application {
    
        private TextField[][] squares;
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            squares = new TextField[9][9];
            GridPane grid = new GridPane();
            for (int i = 0; i < 9; i++) {
                for (int j = 0; j < 9; j++) {
                    squares[i][j] = new TextField();
                    squares[i][j].setPrefHeight(8);
                    squares[i][j].setPrefWidth(25);
                    setTextHandler(squares[i][j], i, j);
                    grid.add(squares[i][j], j, i);
                }
            }
            grid.setAlignment(Pos.CENTER);
    
            Scene scene = new Scene(grid);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        private void setTextHandler(TextField textField, int i, int j){
            textField.setOnKeyPressed(keyEvent -> {
                System.out.println(keyEvent.getCode());
                if(keyEvent.getCode().isArrowKey()) {
                    if (keyEvent.getCode() == KeyCode.UP) {
                        squares[i-1][j].requestFocus();
    
                    } else if (keyEvent.getCode() == KeyCode.DOWN) {
                        squares[i+1][j].requestFocus();
    
                    } else if (keyEvent.getCode() == KeyCode.LEFT) {
                        squares[i][j-1].requestFocus();
    
                    } else if (keyEvent.getCode() == KeyCode.RIGHT) {
                        squares[i][j+1].requestFocus();
                    }
                }
            });
    
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多