2

I'm trying to limit the number of characters which can be inserted in a JavaFX-8 TextField: when the limit is reached endLineReached() method is called. I would like to set the maximum number of chars basing on size (the visible part on GUI) of the textfield. So the code I'm using is

textfield.textProperty().addListener((ov, oldValue, newValue) -> {
            onEndLine(textfield.getText(), textfield.getPrefColumnCount());
        });


public void onEndLine(String text, int prefColumnCount) {
    if (text.length() > prefColumnCount) {
        endLineReached();
    }
}

The problem is that textfield.getPrefColumnCount() not work properly. Furthermore I'm not able to set prefColumnCount value programmatically because the textfield width could change every time the program is re-started (not during execution). The textfield is child of a JavaFX HBox.

Squerut
  • 65
  • 3
  • 10
  • Typically a `TextField` uses a variable-width font, so the number of characters visible depends on what characters were actually typed. So I don't think the problem you've stated ("set the maximum number of characters based on the size") is well-formed. What are you actually trying to do? Prevent the text from scrolling horizontally? – James_D Jan 02 '16 at 15:17
  • Yes, I'm trying to prevent the text scrolls. Probably exists a better way to solve this problem than working on the number of characters in the Textfield: is there a way to work with a specific listener on the TextField? – Squerut Jan 02 '16 at 15:53

1 Answers1

2

If you want to limit the length of the text based solely on the visual width of the text field, that's a little tricky as it depends on many factors (the font, the width of the text field, the padding applied to the text field, in the case of a variable-width font the actual text that has been entered, etc).

Note this seems a strange thing to do (imo), because the user will be able to enter relatively long text if it happens to use narrow characters, but relatively short text if it happens to use wide characters (in this example below I can enter 26 "l"s but only 8 "m"s). Really the text the user is allowed to enter should be based on logical considerations (i.e. business rules), not visual ones. But maybe you have some unusual use-case for this.

You can use a text formatter on the text field to veto the addition of text. To check the width, create a new Text object with the new text, set the font to the same font as the text field, and then check its width. You need to account for padding in the text field as well. Note this is a little fragile, as you don't really know the implementation of the layout of the text field, but this appears to work ok, at least with the JDK version I have.

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextInputControl;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class TextFieldNoScroll extends Application {

    @Override
    public void start(Stage primaryStage) {
        TextField textField = new TextField();
        textField.setTextFormatter(new TextFormatter<String>(change -> {

            if (change.isAdded()) {
                Insets textFieldInsets = change.getControl().getPadding();              
                double horizPadding = textFieldInsets.getLeft() + textFieldInsets.getRight() ;

                Text newText = new Text(change.getControlNewText());
                newText.setFont(((TextInputControl)change.getControl()).getFont());
                double newTextWidth = newText.getBoundsInLocal().getWidth();
                if (newTextWidth + horizPadding > change.getControl().getWidth()) {
                    return null ;
                }
            }

            return change ;
        }));

        VBox root = new VBox(textField);
        root.setAlignment(Pos.CENTER);
        Scene scene = new Scene(root, 120, 120);
        primaryStage.setScene(scene);
        primaryStage.show();
    }


    public static void main(String[] args) {
        launch(args);
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thank you for the snippet. I know this approach depends principally on font, padding, width, etc. of charchters in the text field. The use case I have is a prompt shell (like a Linux shell): when text insertion reach the end of the line, new charachters inserted must be written in new line. The _endLineReached()_ method appends a new textfield under the first one as child of a VBox. And so on. So I'm wondering if there is a different and better approach to resolve this problem... – Squerut Jan 03 '16 at 11:47
  • I think for that use case I'd use a `TextArea`, with `wrapText` set to `true`, and just veto any modifications that were before, or spanned, the last newline character. – James_D Jan 04 '16 at 03:45