我有一个带有几个UI元素的自定义对话框。一些TextField用于数字输入。当按下逃逸键并且焦点位于任何数字文本字段上时,此对话框不会关闭。当焦点位于没有此自定义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 /sf/answers/2173018571/
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);
}
}
Run Code Online (Sandbox Code Playgroud)
在焦点对准时按下逃生键时,如何关闭对话框id?
提供解决方案,我认为这很重要,或者至少有趣之前,要了解为什么有TextFormatter似乎改变的行为Dialog。如果这对您来说无关紧要,请随时跳到答案的结尾。
根据该文件的Button,取消按钮是:
如果场景中没有其他节点消耗它,则该按钮将接受键盘VK_ESC的按下。
该句子的结尾是重要的部分。的方式取消按钮,以及默认的按钮来实现时,是通过注册的加速器与Scene该Button所属。仅当适当的KeyEvent 气泡达到时,才调用这些加速器Scene。如果事件在到达之前就已消耗Scene,则不会调用加速器。
注意:要了解有关JavaFX中事件处理的更多信息,尤其是诸如“气泡”和“已消耗”之类的术语,建议阅读本教程。
A Dialog关于如何以及何时关闭它具有某些规则。这些规则记录在此处的“ 对话框关闭规则”部分中。可以说,基本上一切都取决于ButtonType已将s添加到DialogPane。在您的示例中,您使用以下预定义类型之一:ButtonType.CANCEL。如果您查看该字段的文档,将会看到:
预先限定的
ButtonType,其中显示“取消”,并且具有ButtonBar.ButtonData的ButtonBar.ButtonData.CANCEL_CLOSE。
如果你看一下文件的ButtonData.CANCEL_CLOSE,你会看到:
“取消”或“关闭”按钮的标签。
是取消按钮: True
至少对于默认实现而言,这意味着为之Button创建的ButtonType.CANCEL将是取消按钮。换句话说,Buttonwill将其cancelButton属性设置为true。这就是允许您Dialog通过按键关闭a 的原因Esc。
注意:这是DialogPane#createButton(ButtonType)负责创建适当按钮的方法(可以重写以进行自定义)。尽管该方法的返回类型是Node正常的,如所记录的那样,通常返回的实例Button。
(核心)JavaFX中的每个控件都具有三个组件:控件类,外观类和行为类。后一类负责处理用户输入,例如鼠标和按键事件。在这种情况下,我们关心TextInputControlBehavior和TextFieldBehavior;前者是后者的超类。
注意:与在JavaFX 9中成为公共API的外观类不同,从JavaFX 12.0.2开始,行为类仍是私有API。下面描述的大部分内容都是实现细节。
所述TextInputControlBehavior类寄存器的EventHandler该反作用于Esc被按下的键,调用cancelEdit(KeyEvent)同一类别的方法。如果有一个,此方法的所有基本实现都将转发KeyEvent给TextInputControl的父级(由于某种未知的原因,导致两个事件分派周期)。但是,TextFieldBehavior该类将重写此方法:
Run Code Online (Sandbox Code Playgroud)@Override protected void cancelEdit(KeyEvent event) { TextField textField = getNode(); if (textField.getTextFormatter() != null) { textField.cancelEdit(); event.consume(); } else { super.cancelEdit(event); } }
如您所见,a的存在TextFormatter会导致a KeyEvent被无条件消耗。这将阻止事件到达Scene,取消按钮不会被触发,因此Dialog当Esc按住时按住键时不会关闭TextField。TextFormatter如前所述,当不存在任何超级实现时,只需将事件转发给父级。
呼叫暗示了这种现象的原因TextInputControl#cancelEdit()。该方法具有形式为的“姐妹方法” TextInputControl#commitValue()。如果您查看这两种方法的文档,将会看到:
如果当前正在编辑该字段,则此调用会将文本设置为最后提交的值。
和:
提交当前文本并将其转换为值。
分别。不幸的是,这并不能解释太多,但是如果您看一下实现,它们的目的就很清楚了。一个TextFormatter具有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();
}
});
Run Code Online (Sandbox Code Playgroud)
如果您要包括取消编辑行为(取消而不取消就取消),那么只有在Dialog没有要取消的编辑时才应关闭。查看kleopatra的答案,看看如何确定是否需要取消。如果有要取消的内容,请不要使用该事件,也不要关闭该事件Dialog。如果没有要取消的内容,请执行与上面的代码相同的操作(即消耗并关闭)。
使用事件过滤器是“推荐的方式”吗?这肯定是有效的方法。JavaFX 与大多数(如果不是全部)主流UI工具包一样是事件驱动的。专门针对JavaFX,这意味着对Events或观察Observable[Value]s进行无效/更改。在“ JavaFX之上”构建的框架可以添加自己的机制。由于问题是我们不希望发生的事件被消耗,因此添加您自己的处理程序以实现所需的行为是有效的。
| 归档时间: |
|
| 查看次数: |
361 次 |
| 最近记录: |