Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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.

like image 403
purring pigeon Avatar asked Jun 10 '16 15:06

purring pigeon


People also ask

How do I bold text in a JavaFX label?

Making Text Bold or Italic To make the text look bold, use the FontWeight constant of the font method as shown in Example 8. t. setFont(Font. font("Verdana", FontWeight.

How do I change the text of a JavaFX label?

You can change the text of a label using its setText() method. This can be done while the application is running. Here is an example of setting the text of a JavaFX Label: label.

How do I make a textarea not editable in JavaFX?

You can use following statement in order to make text-area object non-editable with auto scrollbar: textAreaObjectName. setEditable(false);


1 Answers

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());
 }
}
like image 191
Mosalx Avatar answered Sep 28 '22 01:09

Mosalx