JavaFX 2 TextArea:如何阻止它消耗[Enter]和[Tab]

sco*_*ttb 7 textarea event-handling javafx-2

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

TextArea的默认行为是[Tab]插入TextArea并且[Enter]插入换行符.

我知道我需要使用EventFilters来获取我想要的行为,但是我弄错了.我不希望TextArea使用这些事件......我只是希望它让它们"顺利进行".

jew*_*sea 9

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

要实现此行为:

  1. 每个文本区域的回车键按下事件过滤器,复制并定位到文本区域的父节点(包含默认的"确定"按钮).这会导致在窗体上的任何位置按Enter键时触发默认的"确定"按钮.消耗原始输入键按下,这样就不会将新行添加到文本区域的文本中.
  2. 每个文本区域的Tab键按下都会被捕获,并且处理父级的焦点可遍历列表以查找下一个可聚焦控件,并为该控件请求焦点.消耗原始Tab键按下,这样就不会将新的制表符间距添加到文本区域的文本中.

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

textareahandler

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()
        );
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

更新

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