Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX 2.2 TextField maxlength

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?

like image 858
Georgios Syngouroglou Avatar asked Mar 01 '13 14:03

Georgios Syngouroglou


4 Answers

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.

like image 149
ceklock Avatar answered Nov 15 '22 16:11

ceklock


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
like image 39
kleopatra Avatar answered Nov 15 '22 16:11

kleopatra


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

    }
};
like image 8
Sergey Grinev Avatar answered Nov 15 '22 16:11

Sergey Grinev


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.

like image 4
Georgios Syngouroglou Avatar answered Nov 15 '22 17:11

Georgios Syngouroglou