A Potential Pitfall with the TextFormatter Class in JavaFX

Posted on Thu 07 April 2016 in JavaFX

A common use case for the TextFormatter class in JavaFX is a text field that should convert any lower-case character input into an upper-case character. This can be achieved easily by defining a filter for the text formatter as described here. A straightforward implementation could look like this:

private UnaryOperator<Change> getFilter() {
    return change -> {
        String text = change.getText();
        change.setText(text.toUppercase());
        return change;
    };
}

This implementation comes with one pitfall, though. When the (German) user types the character "ß" into the text field, it will be converted into "SS" because that's how Germans roll. So the resulting string is one character longer than the input string. Why is this a problem? Well, the cursor position will be between the two "S" characters after the conversion:

cursor in text field

When the user types in the next character, it will be inserted between the two "S" characters, not behind them. Bummer.

Luckily, the masterminds behind JavaFX give us the tools to correct this unfortunate behaviour since the change object that the filter works on allows for setting the anchor and caret position in the text field. We need to correct them by the difference between the string lengths before and after the conversion:

private UnaryOperator<Change> getFilter() {
    return change -> {
        String originalText = change.getText();
        String upperCaseText = originalText.toUpperCase();
        change.setText(upperCaseText);

        correctAnchorAndCaretPositions(change, originalText);

        return change;
    };
}

private void correctAnchorAndCaretPositions(Change change, String originalText) {
    int diff = change.getText().length() - originalText.length();
    if (diff != 0) {
        change.setAnchor(change.getAnchor() + diff);
        change.setCaretPosition(change.getCaretPosition() + diff);
    }
}

This puts the cursor at the end of the string no matter how many "ß" characters the user types in or pastes into the text field.