JavaFX 8中用TextFormatter和/或UnaryOperator实现整数数字文本字段

18

我正在尝试使用JavaFX 8的TextFormatter创建一个适用于整数的数字TextField。

使用UnaryOperator的解决方案:

UnaryOperator<Change> integerFilter = change -> {
    String input = change.getText();
    if (input.matches("[0-9]*")) { 
        return change;
    }
    return null;
};

myNumericField.setTextFormatter(new TextFormatter<String>(integerFilter));

使用IntegerStringConverter的解决方案:

myNumericField.setTextFormatter(new TextFormatter<>(new IntegerStringConverter()));  

两种解决方案都有自己的问题。通过使用UnaryOperator,我只能输入0到9之间的数字,但我还需要输入负值,比如“-512”,这时符号只允许在第一位。另外,我不想出现像“00016”这样的数字,可是这却仍然是可能的。

使用IntegerStringConverter方法的效果要好得多:任何无效的数字,比如“-16-123”,都会被拒绝,并且像“0123”这样的数字会被转换成“123”。但是,只有在文本被提交(通过按下enter)或者TextField失去焦点时才会发生转换。

是否有一种方式可以强制在每次更新TextField的值时都使用IntegerStringConverter进行转换?

4个回答

31

转换器与过滤器不同:转换器指定如何将文本转换为值,而过滤器则过滤用户可能进行的更改。听起来您想要两者都有,但您希望过滤器更准确地过滤允许的更改。

我通常发现,如果接受更改,检查文本的新值是最容易的。您希望可选地包含一个-,后跟任意数量的数字1-9。允许空字符串很重要,否则用户将无法删除所有内容。

因此,您可能需要类似于以下内容:

UnaryOperator<Change> integerFilter = change -> {
    String newText = change.getControlNewText();
    if (newText.matches("-?([1-9][0-9]*)?")) { 
        return change;
    }
    return null;
};

myNumericField.setTextFormatter(
    new TextFormatter<Integer>(new IntegerStringConverter(), 0, integerFilter));

您甚至可以为过滤器添加更多功能,让它以更智能的方式处理 -,例如:

UnaryOperator<Change> integerFilter = change -> {
    String newText = change.getControlNewText();
    // if proposed change results in a valid value, return change as-is:
    if (newText.matches("-?([1-9][0-9]*)?")) { 
        return change;
    } else if ("-".equals(change.getText()) ) {

        // if user types or pastes a "-" in middle of current text,
        // toggle sign of value:

        if (change.getControlText().startsWith("-")) {
            // if we currently start with a "-", remove first character:
            change.setText("");
            change.setRange(0, 1);
            // since we're deleting a character instead of adding one,
            // the caret position needs to move back one, instead of 
            // moving forward one, so we modify the proposed change to
            // move the caret two places earlier than the proposed change:
            change.setCaretPosition(change.getCaretPosition()-2);
            change.setAnchor(change.getAnchor()-2);
        } else {
            // otherwise just insert at the beginning of the text:
            change.setRange(0, 0);
        }
        return change ;
    }
    // invalid change, veto it by returning null:
    return null;
};

这将允许用户在任何时候按下-,并切换整数的符号。

SSCCE:

import java.util.function.UnaryOperator;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import javafx.util.converter.IntegerStringConverter;

public class IntegerFieldExample extends Application {

    @Override
    public void start(Stage primaryStage) {
        TextField integerField = new TextField();
        UnaryOperator<Change> integerFilter = change -> {
            String newText = change.getControlNewText();
            if (newText.matches("-?([1-9][0-9]*)?")) { 
                return change;
            } else if ("-".equals(change.getText()) ) {
                if (change.getControlText().startsWith("-")) {
                    change.setText("");
                    change.setRange(0, 1);
                    change.setCaretPosition(change.getCaretPosition()-2);
                    change.setAnchor(change.getAnchor()-2);
                    return change ;
                } else {
                    change.setRange(0, 0);
                    return change ;
                }
            }
            return null;
        };
        
        // modified version of standard converter that evaluates an empty string 
        // as zero instead of null:
        StringConverter<Integer> converter = new IntegerStringConverter() {
            @Override
            public Integer fromString(String s) {
                if (s.isEmpty()) return 0 ;
                return super.fromString(s);
            }
        };

        TextFormatter<Integer> textFormatter = 
                new TextFormatter<Integer>(converter, 0, integerFilter);
        integerField.setTextFormatter(textFormatter);
        
        // demo listener:
        textFormatter.valueProperty().addListener((obs, oldValue, newValue) -> System.out.println(newValue));
        
        VBox root = new VBox(5, integerField, new Button("Click Me"));
        root.setAlignment(Pos.CENTER);
        Scene scene = new Scene(root, 300, 120);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

教程

一份关于如何使用TextFormatter的综合教程指南:


2
数字组中的智能使用?!本来想用-?([1-9]?|[1-9][0-9]*),但这个更简洁! - flakes
2
非常感谢您的努力,您的解决方案完美地奏效了! - ShadowEagle

3
有没有一种方法可以在每次更新TextField的值时强制使用IntegerStringConverter进行第二种方法的转换?
虽然@James_D的答案给出了你想要的,但我想添加一个不同的角度。你的想法是在每个按键操作时都帮助用户。这可能有帮助,但也可能让用户感到沮丧。像将内容复制粘贴到文本字段中或在不同位置编辑现有输入这样的操作与手把手的方法不兼容。
你可能想立即应用转换/过滤的原因是,用户可能没有意识到输入无效,并可能错过在切换到下一个字段时进行更正的机会。那么,为什么不限制用户可以输入的内容,而是可视化当前输入是否有效,而不改变文本内容呢?例如,您可以在内容无效时向文本字段添加红色边框。您仍然可以使用StringConverter来实现此目的。
例如:
myNumericField.setTextFormatter(new TextFormatter<>(new IntegerStringConverter()));
myNumericField.textProperty().addListener((obs,oldv,newv) -> {
    try {
        myNumericField.getTextFormatter().getValueConverter().fromString(newv);
        // no exception above means valid
        myNumericField.setBorder(null);
    } catch (NumberFormatException e) {
        myNumericField.setBorder(new Border(new BorderStroke(Color.RED, BorderStrokeStyle.SOLID, new CornerRadii(3), new BorderWidths(2), new Insets(-2))));
    }
});

带红色边框的文本字段

该转换器还可以轻松扩展以限制有效数字范围。


我认为这是一个非常优雅的解决方案:没有正则表达式,格式化、转换和验证之间有清晰的分离(后者在此示例中未显示,但很容易扩展)。感谢您提供这个! - r-uu

0
 TextField txtpoint = new TextField();
    txtpoint.textProperty().addListener(new ChangeListener<String>() {
        @Override
        public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
            if (!newValue.isEmpty()) {
                try {
                    long pointI = Integer.parseInt(newValue);
                    txtpoint.setText(String.valueOf(pointI));
                } catch (Exception e) {
                    txtpoint.clear();
                    txtpoint.setText(getNumber(oldValue));
                }
            }
        }
    });


private String getNumber(String value) {
    String n = "";
    try {
        return String.valueOf(Integer.parseInt(value));
    } catch (Exception e) {
        String[] array = value.split("");
        for (String tab : array) {
            try {
                System.out.println(tab);
                n = n.concat(String.valueOf(Integer.parseInt(String.valueOf(tab))));
            } catch (Exception ex) {
                System.out.println("not nomber");
            }
        }
        return n;
    }
}

4
在Stack Overflow上,通常欢迎一些解释或使用说明的文字。 - mkl
自从fx8u80版本以来,这是__错误的__(您不应更改正在侦听的属性),请改用textFormatter。 - kleopatra

0

以下是我的解决方案:

    integerText.setTextFormatter(new TextFormatter<>(new IntegerStringConverter(), 0, new UnaryOperator<TextFormatter.Change>() {
        @Override
        public TextFormatter.Change apply(TextFormatter.Change change) {
            NumberFormat numberFormat = NumberFormat.getIntegerInstance();
            ParsePosition position = new ParsePosition(0);
            Object obj = numberFormat.parseObject(change.getControlNewText(),position);
            if(obj != null && position.getIndex() == change.getControlNewText().length()){
                return change;
            }
            return null;
        }
    }));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接