Frage

I am working on a JavaFX 2.2 project and I have a problem using the TextField control. I want to limit the number of characters that a user will be able to enter into each TextField. However I can't find a property or something like maxlength. The same problem existed in Swing and was solved this way. How to solve it for JavaFX 2.2?

War es hilfreich?

Lösung

This is a better way to do the job on a generic text field:

public static void addTextLimiter(final TextField tf, final int maxLength) {
    tf.textProperty().addListener(new ChangeListener<String>() {
        @Override
        public void changed(final ObservableValue<? extends String> ov, final String oldValue, final String newValue) {
            if (tf.getText().length() > maxLength) {
                String s = tf.getText().substring(0, maxLength);
                tf.setText(s);
            }
        }
    });
}

Works perfectly, except for that Undo bug.

Andere Tipps

With java8u40 we got a new class TextFormatter: one of its main responsibilities is to provide a hook into any change of text input before it gets comitted to the content. In that hook we can accept/reject or even change the proposed change.

The requirement solved in the OP's self-answer is

  • the rule: restrict the length of text to shorter than n chars
  • the modification: if the rule is violated, keep the last n chars as the input text and remove the excess chars at its start

Using a TextFormatter, this could be implemented like:

// here we adjust the new text 
TextField adjust = new TextField("scrolling: " + len);
UnaryOperator<Change> modifyChange = c -> {
    if (c.isContentChange()) {
        int newLength = c.getControlNewText().length();
        if (newLength > len) {
            // replace the input text with the last len chars
            String tail = c.getControlNewText().substring(newLength - len, newLength);
            c.setText(tail);
            // replace the range to complete text
            // valid coordinates for range is in terms of old text
            int oldLength = c.getControlText().length();
            c.setRange(0, oldLength);
        }
    }
    return c;
};
adjust.setTextFormatter(new TextFormatter(modifyChange));

Asides:

  • modifying a property while listening to its change might lead to unexpected side-effects
  • all suggested solutions on the key-level events are broken (they can't handle paste/programatic changes

You can do something similar to approach described here: http://fxexperience.com/2012/02/restricting-input-on-a-textfield/

class LimitedTextField extends TextField {

    private final int limit;

    public LimitedTextField(int limit) {
        this.limit = limit;
    }

    @Override
    public void replaceText(int start, int end, String text) {
        super.replaceText(start, end, text);
        verify();
    }

    @Override
    public void replaceSelection(String text) {
        super.replaceSelection(text);
        verify();
    }

    private void verify() {
        if (getText().length() > limit) {
            setText(getText().substring(0, limit));
        }

    }
};

The full code i used to solve my problem is the code below. I extend the TextField class like Sergey Grinev done and i added an empty constructor. To set the maxlength i added a setter method. I first check and then replace the text in the TextField because i want to disable inserting more than maxlength characters, otherwise the maxlength + 1 character will be inserted at the end of the TextField and the first charcter of the TextField will be deleted.

package fx.mycontrols;

public class TextFieldLimited extends TextField {  
    private int maxlength;
    public TextFieldLimited() {
        this.maxlength = 10;
    }
    public void setMaxlength(int maxlength) {
        this.maxlength = maxlength;
    }
    @Override
    public void replaceText(int start, int end, String text) {
        // Delete or backspace user input.
        if (text.equals("")) {
            super.replaceText(start, end, text);
        } else if (getText().length() < maxlength) {
            super.replaceText(start, end, text);
        }
    }

    @Override
    public void replaceSelection(String text) {
        // Delete or backspace user input.
        if (text.equals("")) {
            super.replaceSelection(text);
        } else if (getText().length() < maxlength) {
            // Add characters, but don't exceed maxlength.
            if (text.length() > maxlength - getText().length()) {
                text = text.substring(0, maxlength- getText().length());
            }
            super.replaceSelection(text);
        }
    }
}

Inside the fxml file i added the import (of the package that the TextFieldLimited class is existing) on the top of the file and replace the TextField tag with the custom TextFieldLimited.

<?import fx.mycontrols.*?>
.  
.  
. 
<TextFieldLimited fx:id="usernameTxtField" promptText="username" />

Inside the controller class,

on the top (property declaration),
@FXML
private TextFieldLimited usernameTxtField;

inside the initialize method,
usernameTxtField.setLimit(40);

That's all.

I'm using a simpler way to both limit the number of characters and force numeric input:

public TextField data;
public static final int maxLength = 5;

data.textProperty().addListener(new ChangeListener<String>() {
    @Override
    public void changed(ObservableValue<? extends String> observable,
            String oldValue, String newValue) {
        try {
            // force numeric value by resetting to old value if exception is thrown
            Integer.parseInt(newValue);
            // force correct length by resetting to old value if longer than maxLength
            if(newValue.length() > maxLength)
                data.setText(oldValue);
        } catch (Exception e) {
            data.setText(oldValue);
        }
    }
});

The code below will re-position the cursor so the user doesn't accidentally overwrite their input.

public static void setTextLimit(TextField textField, int length) {
    textField.setOnKeyTyped(event -> {
        String string = textField.getText();

        if (string.length() > length) {
            textField.setText(string.substring(0, length));
            textField.positionCaret(string.length());
        }
    });
}

This method let TextField to finish all processing (copy/paste/undo safe). Do not requares to make extending class. And allow you to deside what to do with new text after every change (to push it to logic, or turn back to previous value, or even to modify it).

  // fired by every text property change
textField.textProperty().addListener(
  (observable, oldValue, newValue) -> {
    // Your validation rules, anything you like
      // (! note 1 !) make sure that empty string (newValue.equals("")) 
      //   or initial text is always valid
      //   to prevent inifinity cycle
    // do whatever you want with newValue

    // If newValue is not valid for your rules
    ((StringProperty)observable).setValue(oldValue);
      // (! note 2 !) do not bind textProperty (textProperty().bind(someProperty))
      //   to anything in your code.  TextProperty implementation
      //   of StringProperty in TextFieldControl
      //   will throw RuntimeException in this case on setValue(string) call.
      //   Or catch and handle this exception.

    // If you want to change something in text
      // When it is valid for you with some changes that can be automated.
      // For example change it to upper case
    ((StringProperty)observable).setValue(newValue.toUpperCase());
  }
);

For your case just add this logic inside. Works perfectly.

    // For example 10 characters     
  if (newValue.length() >= 10) ((StringProperty)observable).setValue(oldValue);

I have this bit of code that only allows numbers and limits the input length on a text field in Javafx.

// Event handler for inputPrice
     inputPrice.setOnAction(event2 -> {

            // Obtain input as a String from text field
            String inputPriceStr = inputPrice.getText();

            // Get length of the String to compare with max length
            int length = inputPrice.getText().length();

            final int MAX = 10; // limit number of characters

            // Validate user input allowing only numbers and limit input size
            if (inputPriceStr.matches("[0-9]*") && length < MAX ) {

                 // your code here
             }});
private void registerListener1(TextField tf1, TextField tf2,TextField tf3,TextField tf4) {
    tf1.textProperty().addListener((obs, oldText, newText) -> {

        if(newText.length() == 12) {

            tf1.setText(newText.substring(0, 3));
            tf2.setText(newText.substring(tf1.getText().length(), 6));              
            tf3.setText(newText.substring(tf1.getText().length()+tf2.getText().length(), 9));
            tf4.setText(newText.substring(tf1.getText().length()+tf2.getText().length()+tf3.getText().length()));
            tf4.requestFocus();
        }
    });

}

private void registerListener(TextField tf1, TextField tf2) {
    tf1.textProperty().addListener((obs, oldText, newText) -> {
        if(oldText.length() < 3 && newText.length() >= 3) {
            tf2.requestFocus();
        }
    });

}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top