Javafx 格式化文本字段

Javafx 格式化文本字段,javafx,textfield,Javafx,Textfield,我正在尝试创建一个TextField,其内容通过模板验证。为此,我创建了一个TextFormatter,并将StringConverter传递给它 然而,我确实注意到了使用StringConverter的一件奇怪的事情。当我输入无效数据且该字段失去焦点时,它不会清除其内容(它仅在后续聚焦后清除)。作为比较,当我使用StringConverter时,没有注意到这个问题 如果我捕捉到焦点的变化并验证数据,问题就解决了,但我想知道为什么这两种情况下的验证都不一致 public class Sample

我正在尝试创建一个
TextField
,其内容通过模板验证。为此,我创建了一个
TextFormatter
,并将
StringConverter
传递给它

然而,我确实注意到了使用
StringConverter
的一件奇怪的事情。当我输入无效数据且该字段失去焦点时,它不会清除其内容(它仅在后续聚焦后清除)。作为比较,当我使用
StringConverter
时,没有注意到这个问题

如果我捕捉到焦点的变化并验证数据,问题就解决了,但我想知道为什么这两种情况下的验证都不一致

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();
    }
}
公共类示例扩展应用程序{
公共静态void main(字符串[]args){
发射(args);
}
@凌驾
公共无效开始(阶段primaryStage){
TextField fieldA=新的TextField();
fieldA.SetPrompText(“00000”);
fieldA.setTextFormatter(新的TextFormatter(新的StringConverter)(){
@凌驾
公共字符串到字符串(字符串对象){
如果(object==null)返回“”;
返回对象。匹配(“[0-9]{5}”)?对象:“”;
}
@凌驾
公共字符串fromString(字符串字符串){
if(string==null)返回null;
返回字符串。匹配(“[0-9]{5}”)?字符串:null;
}
}));
//fieldA.focusedProperty().addListener((可观察、旧值、新值)->{
//如果(!fieldA.textProperty().getValueSafe()匹配(“[0-9]{5}”)){
//fieldA.setText(空);
//            }
//        });
TextField字段b=新的TextField();
字段b.setPrompText(“HH:MM:SS”);
fieldB.setTextFormatter(新的TextFormatter(新的StringConverter)(){
@凌驾
公共字符串toString(LocalTime对象){
如果(object==null)返回“”;
返回object.format(模式的DateTimeFormatter.of(“HH:mm:ss”);
}
@凌驾
公共LocalTime fromString(字符串){
if(string==null)返回null;
返回LocalTime.parse(string,DateTimeFormatter.of模式(“HH:mm:ss”);
}
}));
VBox VBox=新的VBox(字段A、字段B);
vBox.setspace(5);
设置场景(新场景(vBox));
primaryStage.show();
}
}

注:注意,目的不是创建只能接受5个数字的
TextField
。这只是一个例子。

我找到了行为差异的原因。主要问题是,通过将
valueProperty
(在
TextFormatter
中)与
textProperty
(在
TextField
中)绑定来更新控件。由于对所有
属性
对象的更改通知仅在包装器的值更改时才会饱和,因此顺序null提交会导致一次性通知

使用
StringConverter
时的不同行为是因为
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();
    }
}
公共类示例扩展应用程序{
公共静态void main(字符串[]args){
发射(args);
}
@凌驾
公共无效开始(阶段primaryStage){
TextField fieldA=新的TextField();
fieldA.SetPrompText(“00000”);
fieldA.setTextFormatter(新的TextFormatter(新的StringConverter)(){
@凌驾
公共字符串到字符串(字符串对象){
如果(object==null)返回“”;
返回对象。匹配(“[0-9]{5}”)?对象:“”;
}
@凌驾
公共字符串fromString(字符串字符串){
if(字符串==null)
抛出新的RuntimeException(“值为null”);
if(string.matches(“[0-9]{5}”)){
返回字符串;
}
抛出新的RuntimeException(“值不匹配”);
}
}));
TextField字段b=新的TextField();
字段b.setPrompText(“HH:MM:SS”);
fieldB.setTextFormatter(新的TextFormatter(新的StringConverter)(){
@凌驾
公共字符串toString(LocalTime对象){
如果(object==null)返回“”;
返回object.format(模式的DateTimeFormatter.of(“HH:mm:ss”);
}
@凌驾
公共LocalTime fromString(字符串){
if(string==null)返回null;
返回LocalTime.parse(string,DateTimeFormatter.of模式(“HH:mm:ss”);
}
}));
VBox VBox=新的VBox(字段A、字段B);
vBox.setspace(5);
设置场景(新场景(vBox));
primaryStage.show();
}
}