从JavaFX对话框中的数字文本字段转义
我有一个带有几个UI元素的自定义对话框。有些文本字段是用于。当按escape键且焦点位于任何数字文本字段上时,此对话框不会关闭。当焦点位于没有此自定义TextFormatter的其他文本字段时,对话框将正常关闭 以下是简化代码:从JavaFX对话框中的数字文本字段转义,javafx,Javafx,我有一个带有几个UI元素的自定义对话框。有些文本字段是用于。当按escape键且焦点位于任何数字文本字段上时,此对话框不会关闭。当焦点位于没有此自定义TextFormatter的其他文本字段时,对话框将正常关闭 以下是简化代码: package application; import java.text.DecimalFormat; import java.text.ParsePosition; import javafx.application.Application; import ja
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);
}
}
包应用;
导入java.text.DecimalFormat;
导入java.text.ParsePosition;
导入javafx.application.application;
导入javafx.application.Platform;
导入javafx.scene.control.ButtonType;
导入javafx.scene.control.Dialog;
导入javafx.scene.control.Label;
导入javafx.scene.control.TextField;
导入javafx.scene.control.TextFormatter;
导入javafx.scene.layout.HBox;
导入javafx.scene.layout.VBox;
导入javafx.stage.stage;
公共类主扩展应用程序{
@凌驾
公共无效开始(阶段primaryStage){
试一试{
TextField name=新的TextField();
HBox hb1=新的HBox();
hb1.getChildren().addAll(新标签(“名称”)、名称);
TextField id=新的TextField();
id.setTextFormatter(getNumberFormatter());//仅限数字
HBox hb2=新的HBox();
hb2.getChildren().addAll(新标签(“ID”)、ID);
VBox VBox=新的VBox();
vbox.getChildren().addAll(hb1,hb2);
Dialog=新建Dialog();
对话框.setTitle(“数字转义”);
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(“确定:+name.getText()+id.getText());
}否则{
系统输出打印项次(“取消”);
}
}捕获(例外e){
e、 printStackTrace();
}
}
TextFormatter getNumberFormatter(){
//从https://stackoverflow.com/a/31043122
DecimalFormat=新的DecimalFormat(“#”);
TextFormatter tf=新的TextFormatter(c->{
if(c.getControlNewText().isEmpty()){
返回c;
}
ParsePosition ParsePosition=新的ParsePosition(0);
Object Object=format.parse(c.getControlNewText(),parsePosition);
if(object==null | | parsePosition.getIndex()
当焦点在id
上时,按下escape键时如何关闭对话框?问题
在提供解决方案之前,我认为理解为什么使用TextFormatter
似乎会改变对话框的行为是重要的,或者至少是有趣的。如果这对你来说无关紧要,请跳到答案的末尾
取消按钮
根据按钮
,取消按钮为:
如果场景中没有其他节点使用它,则接收键盘VK_ESC按下的按钮
那句话的结尾是重要的部分。取消按钮以及默认按钮的实现方式是通过注册按钮所属的场景
。仅当相应的KeyEvent
冒泡进入场景时,才会调用这些加速器。如果事件在到达场景
之前被消耗,则不会调用加速器
注意:要了解JavaFX中事件处理的更多信息,特别是“气泡”和“消费”等术语,我建议阅读
对话
对话框
有关于如何以及何时关闭的特定规则。这些规则位于对话框关闭规则部分。只需说一句,基本上一切都取决于将哪个按钮类型添加到对话框窗格
。在您的示例中,您使用一种预定义类型:ButtonType.CANCEL
。如果您查看该字段的名称,您将看到:
一种预定义的ButtonType
,显示“取消”,并具有ButtonBar.ButtonDa.Cancel的ButtonBar.ButtonDa
如果您查看按钮的数据。取消\u关闭
,您将看到:
“取消”或“关闭”按钮的标签
是取消按钮:真
这意味着,至少对于默认实现而言,为所述ButtonType.CANCEL创建的按钮将是一个取消按钮。换句话说,按钮
将其cancelButton
属性设置为true
。这就是通过按Esc键关闭对话框的功能
注意:它是负责创建相应按钮的方法(可以为自定义覆盖)。虽然该方法的返回类型是节点
,但如文档所述,返回按钮
的实例是典型的
文本格式化程序
(核心)JavaFX中的每个控件都有三个组件:控件类、皮肤类和行为类。后一个类负责处理用户输入,例如鼠标和按键事件。在这种情况下,我们关心TextInputControlBehavior
和TextFieldBehavior
;前者是后者的超类
注意:与皮肤类不同,皮肤类在JavaFX9中成为公共API,行为类在JavaFX12.0.2中仍然是私有API。下面描述的大部分内容都是实现细节
TextInputControlBehavior
类注册一个EventHandler
,它对按下Esc键做出反应,调用cancelEdit(KeyEvent
@Override
protected void cancelEdit(KeyEvent event) {
TextField textField = getNode();
if (textField.getTextFormatter() != null) {
textField.cancelEdit();
event.consume();
} else {
super.cancelEdit(event);
}
}
textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.ESCAPE) {
event.consume();
dialog.close();
}
});
public class TextFieldCancelSO extends Application {
/**
* Returns a boolean to indicate whether the given field has uncommitted
* changes.
*
* @param <T> the type of the formatter's value
* @param field the field to analyse
* @return true if the field has a textFormatter with converter and
* uncommitted changes, false otherwise
*/
public static <T> boolean isDirty(TextField field) {
TextFormatter<T> textFormatter = (TextFormatter<T>) field.getTextFormatter();
if (textFormatter == null || textFormatter.getValueConverter() == null) return false;
String fieldText = field.getText();
StringConverter<T> valueConverter = textFormatter.getValueConverter();
String formatterText = valueConverter.toString(textFormatter.getValue());
// todo: handle empty string vs. null value
return !Objects.equals(fieldText, formatterText);
}
/**
* Install a custom keyMapping for ESCAPE in the inputMap of the given field.
* @param field the textField to configure
*/
protected void installCancel(TextField field) {
// Dirty: reflectively access the behavior
// needs --add-exports at compile- and runtime!
// note: FXUtils is a custom helper class not contained in core fx, use your own
// helper or write the field access code as needed.
TextFieldBehavior behavior = (TextFieldBehavior) FXUtils.invokeGetFieldValue(
TextFieldSkin.class, field.getSkin(), "behavior");
// Dirty: internal api/classes
InputMap inputMap = behavior.getInputMap();
KeyBinding binding = new KeyBinding(KeyCode.ESCAPE);
// custom mapping that delegates to helper method
KeyMapping keyMapping = new KeyMapping(binding, e -> {
cancelEdit(field, e);
});
// by default, mappings consume the event - configure not to
keyMapping.setAutoConsume(false);
// remove old
inputMap.getMappings().remove(keyMapping);
// add new
inputMap.getMappings().add(keyMapping);
}
/**
* Custom EventHandler that's mapped to ESCAPE.
*
* @param field the field to handle a cancel for
* @param ev the received keyEvent
*/
protected void cancelEdit(TextField field, KeyEvent ev) {
boolean dirty = isDirty(field);
field.cancelEdit();
if (dirty) {
ev.consume();
}
}
private Parent createContent() {
TextFormatter<String> fieldFormatter = new TextFormatter<>(
TextFormatter.IDENTITY_STRING_CONVERTER, "textField ...");
TextField field = new TextField();
field.setTextFormatter(fieldFormatter);
// listen to skin: behavior is available only after it's set
field.skinProperty().addListener((src, ov, nv) -> {
installCancel(field);
});
// just to see the state of the formatter
Label fieldValue = new Label();
fieldValue.textProperty().bind(fieldFormatter.valueProperty());
// add cancel button
Button cancel = new Button("I'm the cancel");
cancel.setCancelButton(true);
cancel.setOnAction(e -> LOG.info("triggered: " + cancel.getText()));
HBox fields = new HBox(100, field, fieldValue);
BorderPane content = new BorderPane(fields);
content.setBottom(cancel);
return content;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TextFieldCancelSO.class.getName());
}