How to format a string in a TextField without changing it's value with JavaFX

pur*_*eon 5 javafx-8

I am trying to change the value of a TextField for display only. Ie - when users attempt to enter phone number, they only enter the digits and when they leave the field, it displays formatted without changing the data in the field.

Let's say I have a TextField for a phone number and it should allow digits only, maximum of 10 characters:

2085551212

I can handle that with a TextFormatter using a UnaryOperator.

 UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
      @Override
      public TextFormatter.Change apply(TextFormatter.Change change) {
           int maxlength = 14;

           if(change.getControlText().indexOf('(') == -1) {
                maxlength = 10;
           }

           System.out.println(change);

           if (change.getControlText().length() + change.getText().length() >= maxlength) {
                int maxPos = maxlength - change.getControlText().length();
                change.setText(change.getText().substring(0, maxPos));
           }

           String text = change.getText();

           for (int i = 0; i < text.length(); i++)
                if (!Character.isDigit(text.charAt(i)))
                     return null;

           return change;
      }
 };
Run Code Online (Sandbox Code Playgroud)

However I would like to format the value to be when it's 10 characters long (likely unformatted when != 10):

(208) 555-1212

When I use a TextFormatter to format it, it changes the value of the string to (208) 555-1212. We store only the digits in the database 2085551212.

I attempted this with a StringConverter. But I couldn't make it work. In the toString() method I strip out the formatting, however when I do that my TextField doesn't display.

 StringConverter<String> formatter = new StringConverter<String>() {
      @Override
      public String fromString(String string) {
           System.out.println("fromString(): before = " + string);

           if (string.length() == 14) {
                System.out.println("fromString(): after = " + string);

                return string;
           } else if (string.length() == 10 && string.indexOf('-') == -1) {
                String result =  String.format("(%s) %s-%s", string.substring(0, 3), string.substring(3, 6),
                    string.substring(6, 10));
                System.out.println("fromString(): after = " + result);

                return result;
           } else {
                return null;
           }
      }

      @Override
      public String toString(String object) {

           System.out.println("toString(): before = " + object);

           if(object == null) {
                return "";
           }

           Pattern p = Pattern.compile("[\\p{Punct}\\p{Blank}]", Pattern.UNICODE_CHARACTER_CLASS);
           Matcher m = p.matcher(object);
           object = m.replaceAll("");

           System.out.println("toString(): after = " + object);

           return object;
      }
 };
Run Code Online (Sandbox Code Playgroud)

I bound to a TextField like this which I assumed would work:

 txtPhone.setTextFormatter(new TextFormatter<String>(formatter, null, filter));
 t2.textProperty().bindBidirectional(foo.fooPropertyProperty(), formatter); //I was just testing to see the results in another textfield to see if it would work.
Run Code Online (Sandbox Code Playgroud)

So I am at a loss. I essentially want to allow only digits and then when the user leaves the field present the value in a formatted way - without actually changing the string value that goes to the database.

Mos*_*alx 5

您在转换器中混淆了彼此的目的toString()和方法。将文本编辑器的 value 属性转换为显示的文本,而不是相反。尝试在这些方法中切换代码,它应该可以工作。fromString()toString()

文本字段在失去焦点后不显示任何内容的原因是因为fromString()调用方法并返回 null(从else子句)。这会提交null给编辑器的 value 属性。value 属性的更改通过调用将toString(null)编辑器的 text 属性更改为空字符串来更新显示的文本 (textProperty)。

编辑

下面是我的测试代码,它是评论中讨论的后续内容。我重复使用了大量的原始代码。我创建了一个 FXML JavaFX 项目并TextFieldLabelFXML 文件中进行了定义。接受TextField用户的输入并对其进行格式化。Label应进入数据库的文本格式化程序的显示值(仅数字)。该值可通过调用 来访问formatter.valueProperty().get()。我希望它有帮助。

import java.net.URL;
import java.util.ResourceBundle;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.util.StringConverter;

public class FXMLDocumentController implements Initializable {

// label displays phone number containing only digits (for database)
@FXML private Label label;

/* field displays formatted text (XXX)-XXX-XXXX after user types
10 digits and presses Enter or if the field looses focus */
@FXML private TextField field;

@Override
public void initialize(URL url, ResourceBundle rb) {

    UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
        @Override
        public TextFormatter.Change apply(TextFormatter.Change change) {
            if (!change.isContentChange()) {
                /* nothing is added or deleted but change must be returned 
                 * as it contains selection info and caret position
                 */
                return change;
            }

            int maxlength = 14;
            if (change.getControlText().indexOf('(') == -1) {
                maxlength = 10;
            }

            if (change.getControlNewText().length() > maxlength
                    || change.getText().matches("\\D+")) {
                // invalid input. Cancel the change
                return null;
            }
            return change;
        }
    };

    StringConverter<String> converter = new StringConverter<String>() {

        // updates displayed text from commited value
        @Override
        public String toString(String commitedText) {
            if (commitedText == null) {
                // don't change displayed text
                return field.getText();
            }

            if (commitedText.length() == 10 && !commitedText.matches("\\D+")) {
                return String.format("(%s) %s-%s", commitedText.substring(0, 3), commitedText.substring(3, 6),
                                     commitedText.substring(6, 10));
            } else {
                /* Commited text can be either null or 10 digits.
                 * Nothing else is allowed by fromString() method unless changed directly
                 */
                throw new IllegalStateException(
                        "Unexpected or incomplete phone number value: " + commitedText);
            }
        }

        // commits displayed text to value
        @Override
        public String fromString(String displayedText) {
            // remove formatting characters
            Pattern p = Pattern.compile("[\\p{Punct}\\p{Blank}]", Pattern.UNICODE_CHARACTER_CLASS);
            Matcher m = p.matcher(displayedText);
            displayedText = m.replaceAll("");

            if (displayedText.length() != 10) {
                // user is not done typing the number. Don't commit
                return null;
            }

            return displayedText;
        }
    };
    TextFormatter<String> formatter = new TextFormatter<String>(converter, "1234567890", filter);
    field.setTextFormatter(formatter);
    label.textProperty().bind(formatter.valueProperty());
 }
}
Run Code Online (Sandbox Code Playgroud)

  • 我很抱歉。那是因为我的代码缺少原始代码中的 `object = m.replaceAll("");` 。我不知道这是怎么发生的。我认为使用文本格式化程序是正确的方法。让我第三次再次修正我的答案。新的编辑解决了您发现的问题并包含其他改进。请注意,除非电话号码完整,否则文本不会转换为非空值。这似乎适合您的任务。 (2认同)