如何在不使用JavaFX更改其值的情况下格式化TextField中的字符串 [英] How to format a string in a TextField without changing it's value with JavaFX

查看:59
本文介绍了如何在不使用JavaFX更改其值的情况下格式化TextField中的字符串的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图更改TextField的值,仅用于显示.即-当用户尝试输入电话号码时,他们只输入数字,而当他们离开该字段时,它将显示格式,而不会更改该字段中的数据.

假设我有一个电话号码TextField,并且只能输入数字,最多10个字符:

2085551212

我可以使用UnaryOperator通过TextFormatter处理.

 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;
      }
 };

但是我想将值格式化为10个字符长(当!= 10时可能未格式化):

(208)555-1212

当我使用TextFormatter对其进行格式化时,它会将字符串的值更改为(208)555-1212.我们仅将数字存储在数据库2085551212中.

我尝试使用StringConverter进行此操作.但是我无法使其工作.在toString()方法中,我去除了格式,但是当我这样做时,我的TextField不显示.

 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;
      }
 };

我绑定到了一个我认为会起作用的TextField:

 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.

所以我很茫然.我本质上只想允许数字,然后当用户离开字段时以格式化的方式显示值-而不实际更改进入数据库的字符串值.

解决方案

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

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

编辑

下面是我的测试代码,它是注释中讨论的后续内容.我重复使用了大量原始代码.我创建了一个FXML JavaFX项目,并在FXML文件中定义了TextFieldLabel. 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());
 }
}

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;
      }
 };

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;
      }
 };

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.

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.

解决方案

You are confusing the purpose of toString() and fromString() methods with each other in your converter. toString() converts the value property of your text editor to the displayed text, not the other way around. Try switching the code in these methods and it should work.

The reason why your text field does not display anything after loosing focus is because fromString() method is called and returns null (from the else clause). This commits null to the value property of your editor. The change in value property updates the displayed text (textProperty) by calling toString(null) which changes the text property of your editor to an empty string.

EDIT

Below is my test code that is a follow-up to the discussion in the comments. I reused a large amount of your original code. I created an FXML JavaFX project and defined TextField and Label in FXML file. The TextField accepts user's input and formats it. The Label displays value of the text formatter (only digits) that should go to the database. The value is accessible by calling formatter.valueProperty().get(). I hope it helps.

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());
 }
}

这篇关于如何在不使用JavaFX更改其值的情况下格式化TextField中的字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆