格式化文本域
Formatted TextField
我正在尝试创建一个 TextField
其内容已使用模板进行验证。为此,我创建了一个 TextFormatter
并向其传递了一个 StringConverter
.
但是,我确实注意到使用 StringConverter<String>
有一件奇怪的事情。当我输入无效数据并且该字段失去焦点时,它不会清除其内容(它只会在后续聚焦后清除它)。作为对比,当我使用 StringConverter<LocalTime>
时,这个问题没有被注意到。
如果我捕捉到焦点的变化并验证数据,问题就解决了,但我想知道为什么两种情况下的验证都存在差异。
public class Sample extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
TextField fieldA = new TextField();
fieldA.setPromptText("00000");
fieldA.setTextFormatter(new TextFormatter<>(new StringConverter<String>() {
@Override
public String toString(String object) {
if(object == null) return "";
return object.matches("[0-9]{5}") ? object : "";
}
@Override
public String fromString(String string) {
if(string == null) return null;
return string.matches("[0-9]{5}") ? string : null;
}
}));
// fieldA.focusedProperty().addListener((observable, oldValue, newValue) -> {
// if(!fieldA.textProperty().getValueSafe().matches("[0-9]{5}")) {
// fieldA.setText(null);
// }
// });
TextField fieldB = new TextField();
fieldB.setPromptText("HH:MM:SS");
fieldB.setTextFormatter(new TextFormatter<>(new StringConverter<LocalTime>() {
@Override
public String toString(LocalTime object) {
if(object == null) return "";
return object.format(DateTimeFormatter.ofPattern("HH:mm:ss"));
}
@Override
public LocalTime fromString(String string) {
if(string == null) return null;
return LocalTime.parse(string, DateTimeFormatter.ofPattern("HH:mm:ss"));
}
}));
VBox vBox = new VBox(fieldA, fieldB);
vBox.setSpacing(5);
primaryStage.setScene(new Scene(vBox));
primaryStage.show();
}
}
ps:注意,目的不是创建一个只能接受5个数字的TextField
。这只是一个例子。
我找到了行为差异的原因。主要问题是更新控件是通过将 valueProperty
(在 TextFormatter
中)与 textProperty
(在 TextField
中)绑定来完成的。因为所有 Property
对象的更改通知仅在包装器的值更改时饱和,顺序 null 提交导致一次性通知。
使用 StringConverter<LocalTime>
时的不同行为是因为 LocalTime::parse()
在无效格式中抛出 DateTimeParseException
异常。这反过来会导致设置新的 valueProperty
值,并导致先前的有效控制值。
这是导致此行为的 TextFormatter
的特定片段。
void updateValue(String text) {
if (!value.isBound()) {
try {
V v = valueConverter.fromString(text);
setValue(v);
} catch (Exception e) {
updateText(); // Set the text with the latest value
}
}
}
问题的解决方案是使用无效值实现 StringConverter::fromString
,而不是返回 null,应该抛出未经检查的异常。
public class Sample extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
TextField fieldA = new TextField();
fieldA.setPromptText("00000");
fieldA.setTextFormatter(new TextFormatter<>(new StringConverter<String>() {
@Override
public String toString(String object) {
if(object == null) return "";
return object.matches("[0-9]{5}") ? object : "";
}
@Override
public String fromString(String string) {
if(string == null)
throw new RuntimeException("Value is null");
if(string.matches("[0-9]{5}")) {
return string;
}
throw new RuntimeException("Value not match");
}
}));
TextField fieldB = new TextField();
fieldB.setPromptText("HH:MM:SS");
fieldB.setTextFormatter(new TextFormatter<>(new StringConverter<LocalTime>() {
@Override
public String toString(LocalTime object) {
if(object == null) return "";
return object.format(DateTimeFormatter.ofPattern("HH:mm:ss"));
}
@Override
public LocalTime fromString(String string) {
if(string == null) return null;
return LocalTime.parse(string, DateTimeFormatter.ofPattern("HH:mm:ss"));
}
}));
VBox vBox = new VBox(fieldA, fieldB);
vBox.setSpacing(5);
primaryStage.setScene(new Scene(vBox));
primaryStage.show();
}
}
我正在尝试创建一个 TextField
其内容已使用模板进行验证。为此,我创建了一个 TextFormatter
并向其传递了一个 StringConverter
.
但是,我确实注意到使用 StringConverter<String>
有一件奇怪的事情。当我输入无效数据并且该字段失去焦点时,它不会清除其内容(它只会在后续聚焦后清除它)。作为对比,当我使用 StringConverter<LocalTime>
时,这个问题没有被注意到。
如果我捕捉到焦点的变化并验证数据,问题就解决了,但我想知道为什么两种情况下的验证都存在差异。
public class Sample extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
TextField fieldA = new TextField();
fieldA.setPromptText("00000");
fieldA.setTextFormatter(new TextFormatter<>(new StringConverter<String>() {
@Override
public String toString(String object) {
if(object == null) return "";
return object.matches("[0-9]{5}") ? object : "";
}
@Override
public String fromString(String string) {
if(string == null) return null;
return string.matches("[0-9]{5}") ? string : null;
}
}));
// fieldA.focusedProperty().addListener((observable, oldValue, newValue) -> {
// if(!fieldA.textProperty().getValueSafe().matches("[0-9]{5}")) {
// fieldA.setText(null);
// }
// });
TextField fieldB = new TextField();
fieldB.setPromptText("HH:MM:SS");
fieldB.setTextFormatter(new TextFormatter<>(new StringConverter<LocalTime>() {
@Override
public String toString(LocalTime object) {
if(object == null) return "";
return object.format(DateTimeFormatter.ofPattern("HH:mm:ss"));
}
@Override
public LocalTime fromString(String string) {
if(string == null) return null;
return LocalTime.parse(string, DateTimeFormatter.ofPattern("HH:mm:ss"));
}
}));
VBox vBox = new VBox(fieldA, fieldB);
vBox.setSpacing(5);
primaryStage.setScene(new Scene(vBox));
primaryStage.show();
}
}
ps:注意,目的不是创建一个只能接受5个数字的TextField
。这只是一个例子。
我找到了行为差异的原因。主要问题是更新控件是通过将 valueProperty
(在 TextFormatter
中)与 textProperty
(在 TextField
中)绑定来完成的。因为所有 Property
对象的更改通知仅在包装器的值更改时饱和,顺序 null 提交导致一次性通知。
使用 StringConverter<LocalTime>
时的不同行为是因为 LocalTime::parse()
在无效格式中抛出 DateTimeParseException
异常。这反过来会导致设置新的 valueProperty
值,并导致先前的有效控制值。
这是导致此行为的 TextFormatter
的特定片段。
void updateValue(String text) {
if (!value.isBound()) {
try {
V v = valueConverter.fromString(text);
setValue(v);
} catch (Exception e) {
updateText(); // Set the text with the latest value
}
}
}
问题的解决方案是使用无效值实现 StringConverter::fromString
,而不是返回 null,应该抛出未经检查的异常。
public class Sample extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
TextField fieldA = new TextField();
fieldA.setPromptText("00000");
fieldA.setTextFormatter(new TextFormatter<>(new StringConverter<String>() {
@Override
public String toString(String object) {
if(object == null) return "";
return object.matches("[0-9]{5}") ? object : "";
}
@Override
public String fromString(String string) {
if(string == null)
throw new RuntimeException("Value is null");
if(string.matches("[0-9]{5}")) {
return string;
}
throw new RuntimeException("Value not match");
}
}));
TextField fieldB = new TextField();
fieldB.setPromptText("HH:MM:SS");
fieldB.setTextFormatter(new TextFormatter<>(new StringConverter<LocalTime>() {
@Override
public String toString(LocalTime object) {
if(object == null) return "";
return object.format(DateTimeFormatter.ofPattern("HH:mm:ss"));
}
@Override
public LocalTime fromString(String string) {
if(string == null) return null;
return LocalTime.parse(string, DateTimeFormatter.ofPattern("HH:mm:ss"));
}
}));
VBox vBox = new VBox(fieldA, fieldB);
vBox.setSpacing(5);
primaryStage.setScene(new Scene(vBox));
primaryStage.show();
}
}