Search code examples
javafxtextfieldchangelistener

Javafx detect textfield change and determine caret position


I'm implementing the AutoComplete logic in Javafx. A TextField is for user input and every time it got updated, the listener will be triggered. And then a dropdown menu which is a listView will display the matched results.

So the problem is that how can I get the updated caret position in TextField when the textProperty listener is in process?

I'm currently using textfield.getCaretPosition(), but it will only return the previous position.

Any inputs will be appreciated!

        commandTextField.textProperty().addListener(new ChangeListener<String>() {

            @Override
            public void changed(ObservableValue<? extends String> observable,
                                String oldText, String newText) {

                // Following line will return previous caret position
                commandTextField.getCaretPosition();

                ...//autocomplete logic omitted
            }
        });

EDIT: The reason that I need to get the caret position is that I need to know the position where the input is changed and show suggestions correspondingly.

            textField.caretPositionProperty().addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {
                caretPosition = newValue.intValue();
            });

            textField.textProperty().addListener((ChangeListener<String>) (observable, oldValue, newValue) -> {

                ...//autocomplete Logic
            }

In the above case, the textProperty listener will be invoked first and followed by caretPositionProperty, therefore there is no way to get updated caret position in my case.


Solution

  • It's not the intended use of the API, but you could use a TextFormatter constructed with an UnaryOperator<TextFormatter.Change> as parameter. This is usually used to prevent/modify undesired changes to the text, but in this case we'll just use the Change object to get you the data you want.

    The following example "suggests" possibilities each one replacing the selected section with some string:

    @Override
    public void start(Stage primaryStage) throws Exception {
        ListView<String> listView = new ListView<>();
    
        String[] replacements = { "foo", "bar", "42", "zap" };
        listView.getItems().setAll(replacements);
    
        TextField textField = new TextField();
        TextFormatter<?> formatter = new TextFormatter<>((UnaryOperator<TextFormatter.Change>) change -> {
            int a = change.getAnchor();
            int b = change.getCaretPosition();
            String newText = change.getControlNewText();
    
            String prefix = newText.substring(0, Math.min(a, b));
            String suffix = newText.substring(Math.max(a, b));
    
            listView.getItems().clear();
    
            for (String mid : replacements) {
                listView.getItems().add(prefix + mid + suffix);
            }
    
            // do not change anything about the modification
            return change;
        });
        textField.setTextFormatter(formatter);
    
        Scene scene = new Scene(new VBox(listView, textField));
    
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    

    There are plenty more properies available including the text and carret and anchor positions before the change, see the javadoc.