此处的解决方案显示两个文本区域和一个默认按钮。当用户按下 tab 键时,焦点向下移动到下一个控件。当用户按下回车键时,会触发默认按钮。
要实现此行为:
- 每个文本区域的输入键被事件过滤器捕获,复制并定位到文本区域的父节点(其中包含默认的确定按钮)。这会导致在表单上的任意位置按下 enter 时触发默认的 OK 按钮。原来的回车键被消耗,所以它不会导致新的一行被添加到文本区域的文本中。
- 每个文本区域的 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 将新行或制表符空间插入文本区域。