7

我想使用 JavaFX TextArea,就好像它与多行 TextField 完全一样。换句话说,当我按下 [Tab] 时,我想循环到窗体上的下一个控件,当我按下 [Enter] 时,我想让 Key.Event 转到 defaultButton 控件(而不是被 TextArea 消耗)。

TextArea 的默认行为是将 [Tab] 插入到 TextArea 中,而 [Enter] 插入换行符。

我知道我需要使用 EventFilters 来获得我想要的行为,但我完全错了。我不希望 TextArea 消耗这些事件......我只是想让它们“继续前进”。

4

1 回答 1

9

此处的解决方案显示两个文本区域和一个默认按钮。当用户按下 tab 键时,焦点向下移动到下一个控件。当用户按下回车键时,会触发默认按钮。

要实现此行为:

  1. 每个文本区域的输入键被事件过滤器捕获,复制并定位到文本区域的父节点(其中包含默认的确定按钮)。这会导致在表单上的任意位置按下 enter 时触发默认的 OK 按钮。原来的回车键被消耗,所以它不会导致新的一行被添加到文本区域的文本中。
  2. 每个文本区域的 tab 键按下被过滤器捕获,并且父级的焦点可遍历列表被处理以找到下一个可聚焦的控件,并为该控件请求焦点。原来的制表键按下被消耗,因此它不会导致新的制表符间距被添加到文本区域的文本中。

该代码利用了 Java 8 中实现的功能,因此需要Java 8来执行它。

文本区域处理程序

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.value.*;
import javafx.collections.ObservableList;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.control.*;
import static javafx.scene.input.KeyCode.ENTER;
import static javafx.scene.input.KeyCode.TAB;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.*;

public class TextAreaTabAndEnterHandler extends Application {
  final Label status = new Label();

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

  @Override public void start(final Stage stage) {
    final TextArea textArea1 = new TabAndEnterIgnoringTextArea();
    final TextArea textArea2 = new TabAndEnterIgnoringTextArea();

    final Button defaultButton = new Button("OK");
    defaultButton.setDefaultButton(true);
    defaultButton.setOnAction(new EventHandler<ActionEvent>() {
      @Override public void handle(ActionEvent event) {
        status.setText("Default Button Pressed");
      }
    });

    textArea1.textProperty().addListener(new ClearStatusListener());
    textArea2.textProperty().addListener(new ClearStatusListener());

    VBox layout = new VBox(10);
    layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10px;");
    layout.getChildren().setAll(
      textArea1, 
      textArea2, 
      defaultButton, 
      status
    );

    stage.setScene(
      new Scene(layout)
    );
    stage.show();
  }

  class ClearStatusListener implements ChangeListener<String> {
    @Override public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
      status.setText("");
    }
  }

  class TabAndEnterIgnoringTextArea extends TextArea {
    final TextArea myTextArea = this;

    TabAndEnterIgnoringTextArea() {
      addEventFilter(KeyEvent.KEY_PRESSED, new TabAndEnterHandler());
    }

    class TabAndEnterHandler implements EventHandler<KeyEvent> {
      private KeyEvent recodedEvent;

      @Override public void handle(KeyEvent event) {
        if (recodedEvent != null) {
          recodedEvent = null;
          return;
        }

        Parent parent = myTextArea.getParent();
        if (parent != null) {
          switch (event.getCode()) {
            case ENTER:
              if (event.isControlDown()) {
                recodedEvent = recodeWithoutControlDown(event);
                myTextArea.fireEvent(recodedEvent);
              } else {
                Event parentEvent = event.copyFor(parent, parent);
                myTextArea.getParent().fireEvent(parentEvent);
              }  
              event.consume();
              break;

            case TAB:
              if (event.isControlDown()) {
                recodedEvent = recodeWithoutControlDown(event);
                myTextArea.fireEvent(recodedEvent);
              } else {
                ObservableList<Node> children = parent.getChildrenUnmodifiable();
                int idx = children.indexOf(myTextArea);
                if (idx >= 0) {
                  for (int i = idx + 1; i < children.size(); i++) {
                    if (children.get(i).isFocusTraversable()) {
                      children.get(i).requestFocus();
                      break;
                    }
                  }
                  for (int i = 0; i < idx; i++) {
                    if (children.get(i).isFocusTraversable()) {
                      children.get(i).requestFocus();
                      break;
                    }
                  }
                }
              }  
              event.consume();
              break;
          }
        }  
      }

      private KeyEvent recodeWithoutControlDown(KeyEvent event) {
        return new KeyEvent(
          event.getEventType(), 
          event.getCharacter(), 
          event.getText(), 
          event.getCode(), 
          event.isShiftDown(), 
          false, 
          event.isAltDown(), 
          event.isMetaDown()
        );
      }
    }
  }
}

另一种解决方案是为 TextArea 实现您自己的自定义皮肤,其中包括新的密钥处理行为。我相信这样的过程会比这里提出的解决方案更复杂。

更新

关于这个问题的原始解决方案,我不太喜欢的一件事是,一旦使用了 Tab 或 Enter 键,就无法触发它们的默认处理。所以我更新了解决方案,如果用户在按下 Tab 或 Enter 时按住 control 键,将执行默认的 Tab 或 Enter 操作。此更新后的逻辑允许用户通过按 CTRL+Enter 或 CTRL+Tab 将新行或制表符空间插入文本区域。

于 2013-05-20T20:20:31.153 回答