简体   繁体   English

使用 JavaFX 中的箭头键导航出 GridPane 中的 TextField

[英]Navigating out of a TextField in a GridPane using the arrow keys in JavaFX

I am making a sodoku solver program in Java with the JavaFX library.我正在使用 JavaFX 库在 Java 中制作一个数独求解器程序。 The program incorporates an interactive sodoku board consisting of a series of TextField s in a GridPane .该程序包含一个交互式数独板,该板由GridPane中的一系列TextField组成。 The board looks like this:板看起来像这样:

数独板

Right now, the cursor is in the top left most TextField .现在, cursor 位于最左上角的TextField中。 If the field had text in it, the user would be able to move the cursor through the text by using the arrow keys.如果该字段中有文本,用户将能够使用箭头键在文本中移动 cursor。 However, I want the user to be able to use the arrow keys to navigate to a different TextField .但是,我希望用户能够使用箭头键导航到不同的TextField The issue is, the field is in "typing mode" (I don't know the official terminology) so the arrow keys only move the cursor to a different point in the text, but otherwise it stays in the same field.问题是,该字段处于“打字模式”(我不知道官方术语),因此箭头键只会将 cursor 移动到文本中的不同点,否则它会停留在同一字段中。

This is what I mean:这就是我的意思:

在此处输入图像描述

Pretend that line I drew is the cursor.假设我画的那条线是 cursor。 Right now, if I click the left arrow key, the cursor will move to the left of the 1, but I want it to move the the TextField on the left, instead.现在,如果我单击左箭头键,cursor 将移动到 1 的左侧,但我希望它移动左侧的TextField If I click the down arrow key, nothing happens because there is no text below the 1 for the cursor to navigate to, but I want it to move to the TextField below, instead.如果我单击向下箭头键,则不会发生任何事情,因为 1 下方没有文本供 cursor 导航到,但我希望它移至下面的TextField

The code for the GridPane is this: 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);

The squares array is for me to have access to individual TextField s in the GridPane . squares数组让我可以访问GridPane中的单个TextField

Any suggestions for how I can fix this?关于如何解决这个问题的任何建议?

You need to set the event handler to move on arrow key press as you can see below look at the setTextHandler function there is no error handling I just wrote this up to give you an idea of what you should be doing it is called from the loop when you create the TextField s from there it checks for an arrow key press and from there it will .requestFocus() from the next TextField您需要将事件处理程序设置为在箭头键按下时移动,如下所示查看setTextHandler function 没有错误处理 我刚刚写了这个让您了解应该做什么 从循环中调用它当您从那里创建TextField时,它会检查箭头键按下,然后从那里它将.requestFocus()从下一个TextField

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();
                }
            }
        });

    }
}

To avoid the focused TextField from handling arrow keys at all you need to intercept the KeyEvent before it reaches said TextField .为了完全避免聚焦的TextField处理箭头键,您需要在KeyEvent到达所述TextField之前拦截它。 This can be accomplished by adding an event filter to the GridPane and consuming the event as appropriate.这可以通过向GridPane添加事件过滤器并酌情使用事件来完成。 If you're not sure why this works you can check out the JavaFX: Handling Events tutorial.如果您不确定为什么会这样,您可以查看JavaFX:处理事件教程。

Then you can use Node#requestFocus() to programmatically change the focused node.然后您可以使用Node#requestFocus()以编程方式更改焦点节点。

I also recommend setting the prefColumnCount of each TextField rather than trying to set the preferred dimensions manually.我还建议设置每个TextFieldprefColumnCount ,而不是尝试手动设置首选尺寸。 That way the preferred dimensions are computed based on the font size.这样,首选尺寸是根据字体大小计算的。

Here's an example:这是一个例子:

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;
  }
}

The above also adds a TextFormatter to each TextField to show a way to limit the text to digits between 1 and 9. Note the arrow navigation does not "wrap around" when it reaches the end of a row or column.上面还为每个TextField添加了一个TextFormatter以显示一种将文本限制为 1 到 9 之间的数字的方法。请注意,箭头导航在到达行或列的末尾时不会“环绕”。 You can of course modify the code to implement this, if desired.如果需要,您当然可以修改代码来实现这一点。

You may want to consider creating a model for the game.您可能需要考虑为游戏创建 model。 That way the business logic is not tied directly to JavaFX UI objects.这样,业务逻辑就不会直接绑定到 JavaFX UI 对象。 When you update the model it would notify the view (possibly via a "view model", depending on the architecture) and the view will update itself accordingly.当您更新 model 时,它会通知视图(可能通过“视图模型”,具体取决于架构),并且视图将相应地更新自身。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM