javafx - 从 JavaFX 对话框中的数字文本字段中转义
问题描述
我有一个包含多个 UI 元素的自定义对话框。一些 TextFields 用于数字输入。当按下转义键并且焦点位于任何数字文本字段上时,此对话框不会关闭。当焦点位于没有此自定义 TextFormatter 的其他 TextField 上时,对话框会正常关闭。
这是简化的代码:
package application;
import java.text.DecimalFormat;
import java.text.ParsePosition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
TextField name = new TextField();
HBox hb1 = new HBox();
hb1.getChildren().addAll(new Label("Name: "), name);
TextField id = new TextField();
id.setTextFormatter(getNumberFormatter()); // numbers only
HBox hb2 = new HBox();
hb2.getChildren().addAll(new Label("ID: "), id);
VBox vbox = new VBox();
vbox.getChildren().addAll(hb1, hb2);
Dialog<ButtonType> dialog = new Dialog<>();
dialog.setTitle("Number Escape");
dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
dialog.getDialogPane().setContent(vbox);
Platform.runLater(() -> name.requestFocus());
if (dialog.showAndWait().get() == ButtonType.OK) {
System.out.println("OK: " + name.getText() + id.getText());
} else {
System.out.println("Cancel");
}
} catch (Exception e) {
e.printStackTrace();
}
}
TextFormatter<Number> getNumberFormatter() {
// from https://stackoverflow.com/a/31043122
DecimalFormat format = new DecimalFormat("#");
TextFormatter<Number> tf = new TextFormatter<>(c -> {
if (c.getControlNewText().isEmpty()) {
return c;
}
ParsePosition parsePosition = new ParsePosition(0);
Object object = format.parse(c.getControlNewText(), parsePosition);
if (object == null || parsePosition.getIndex() < c.getControlNewText().length()) {
return null;
} else {
return c;
}
});
return tf;
}
public static void main(String[] args) {
launch(args);
}
}
在焦点打开时按下转义键时如何关闭对话框id
?
解决方案
问题
在提供解决方案之前,我认为了解为什么拥有一个TextFormatter
似乎会改变Dialog
. 如果这对您不重要,请随时跳到答案的末尾。
取消按钮
根据的文档,Button
取消按钮是:
如果场景中没有其他节点使用它,则接收键盘 VK_ESC 按下的按钮。
这句话的结尾是重要的部分。取消按钮以及默认按钮的实现方式是通过向所属的注册一个加速器。这些加速器只有在适当的冒泡到. 如果事件在到达 之前被消费,则不会调用加速器。Scene
Button
KeyEvent
Scene
Scene
注意:要了解有关 JavaFX 中事件处理的更多信息,尤其是“气泡”和“消耗”等术语,建议阅读本教程。
对话框
ADialog
有一些关于如何以及何时关闭的规则。这些规则记录在这里,在对话结束规则部分。可以说,基本上一切都取决于ButtonType
添加到DialogPane
. 在您的示例中,您使用其中一种预定义类型:ButtonType.CANCEL
. 如果您查看该字段的文档,您会看到:
ButtonType
显示“取消”并具有 的预定ButtonBar.ButtonData
义ButtonBar.ButtonData.CANCEL_CLOSE
。
如果您查看的文档,ButtonData.CANCEL_CLOSE
您会看到:
“取消”或“关闭”按钮的标签。
是取消按钮: True
这意味着,至少对于默认实现而言,为所述Button
创建的ButtonType.CANCEL
将是一个取消按钮。换句话说,Button
将其cancelButton
属性设置为true
。这就是允许Dialog
通过按键关闭 a 的原因Esc。
注意:它是DialogPane#createButton(ButtonType)
负责创建适当按钮的方法(并且可以被覆盖以进行自定义)。虽然该方法的返回类型Node
是典型的,如文档所述,返回一个Button
.
文本格式化程序
(核心)JavaFX 中的每个控件都具有三个组件:控件类、外观类和行为类。后一个类负责处理用户输入,例如鼠标和按键事件。在这种情况下,我们关心TextInputControlBehavior
and TextFieldBehavior
; 前者是后者的超类。
注意:与在 JavaFX 9 中成为公共 API 的皮肤类不同,行为类在 JavaFX 12.0.2 中仍然是私有 API。下面描述的大部分内容都是实现细节。
该类TextInputControlBehavior
注册一个对被按下的键EventHandler
做出反应的,调用同一类的方法。此方法的所有基本实现都是将 转发给的父级,如果它有一个 - 由于某些未知(对我而言)原因导致两个事件分派周期。但是,该类会覆盖此方法:EsccancelEdit(KeyEvent)
KeyEvent
TextInputControl
TextFieldBehavior
@Override protected void cancelEdit(KeyEvent event) { TextField textField = getNode(); if (textField.getTextFormatter() != null) { textField.cancelEdit(); event.consume(); } else { super.cancelEdit(event); } }
如您所见, a 的存在TextFormatter
导致KeyEvent
无条件地消耗。这会阻止事件到达Scene
,取消按钮不会被触发,因此当有焦点时按下键Dialog
时 不会关闭。当没有调用超级实现时,如前所述,它只是将事件转发给父级。EscTextField
TextFormatter
对 的调用暗示了这种行为的原因TextInputControl#cancelEdit()
。该方法有一个形式为 的“姊妹方法” TextInputControl#commitValue()
。如果您查看这两种方法的文档,您会看到:
如果当前正在编辑该字段,则此调用会将文本设置为最后提交的值。
和:
提交当前文本并将其转换为一个值。
分别。不幸的是,这并不能解释太多,但是如果您查看实现,它们的目的就会变得清晰。ATextFormatter
有一个value
属性,在输入TextField
. 相反,该值仅在提交时更新(例如通过按Enter)。反之亦然;当前文本可以通过取消编辑(例如按Esc)恢复为当前值。
注意:String
与任意类型的对象之间的转换由StringConverter
与TextFormatter
.
当有 时TextFormatter
,取消编辑的行为被认为是一个消耗事件的场景。我想这是有道理的。然而,即使没有什么可以取消的事件仍然被消耗——这对我来说没有多大意义。
一个办法
解决此问题的一种方法是使用反射深入研究内部结构,如kleopatra 的回答所示。另一种选择是将事件过滤器添加到在按下键时关闭的TextField
或某些祖先。TextField
Dialog
Esc
textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.ESCAPE) {
event.consume();
dialog.close();
}
});
如果您想包含取消编辑行为(取消而不关闭),那么您应该只在Dialog
没有要取消的编辑时关闭。看看 kleopatra 的回答,看看如何确定是否需要取消。如果有什么要取消,请不要使用该事件,也不要关闭Dialog
. 如果没有什么要取消的,那么只需执行与上面的代码相同的操作(即使用并关闭)。
使用事件过滤器是“推荐的方式”吗?这当然是一种有效的方式。JavaFX与大多数(如果不是全部)主流 UI 工具包一样是事件驱动的。对于 JavaFX,这意味着对Event
s 做出反应或观察Observable[Value]
s 的失效/更改。“建立在”JavaFX 之上的框架可能会添加自己的机制。由于问题是我们不希望使用的事件,因此添加您自己的处理程序以实现所需的行为是有效的。
推荐阅读
- java - cucumber.runtime.CucumberException:Arity 不匹配:步骤定义
- laravel - Laravel Eloquent“在 null 上调用成员函数 pluck()”错误
- r - 使用 R 将聚合应用于定义的函数
- javascript - 如何在谷歌地图上绘制多个标记?
- php - PHP - 组合数组的折线图
- python - 通过 Paramiko 的 SFTPClient 的无密码连接不起作用
- c# - 检查字典中是否有给定的字符串
- sql - 如何从 PostgresSQL 中的 MAX() 聚合函数中获取两个值
- python - 如何计算熊猫数据框中列值更改的频率
- java - 插入和搜索二叉搜索树