JavaFX: Run ChangeListener

11k views Asked by At

Ho can I run the change method inside ChangeListener in the initialization. Since it only run when changes happen. For example I have a TextField that I want to set it's value to 0 when it's empty, and I do it like this:

textField.textProperty().addListener(new ChangeListener<String>() {
        @Override
        public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
            if (!newValue.matches("\\d*")) {
                textField.setText(newValue.replaceAll("[^\\d]", ""));
            }

            if (newValue.isEmpty())
                textField.setText("0");

        }
    });

But at the start of the application the changed method is not called. So how to work around this issue?

1

There are 1 answers

0
James_D On BEST ANSWER

There's no way to access a listener added to a property. Obviously you could just keep a reference to it:

ChangeListener<String> textFieldListener = (observable, oldValue, newValue) -> {

    if (!newValue.matches("\\d*")) {
        textField.setText(newValue.replaceAll("[^\\d]", ""));
    }

    if (newValue.isEmpty())
        textField.setText("0");

};

textField.textProperty().addListener(textFieldListener);
textFieldListener.changed(null, null, textField.getText());

or, perhaps more naturally, just move the actual functionality to a different method:

textField.textProperty().addListener((observable, oldValue, newValue) -> vetoTextFieldChanges());
vetoTextFieldChanges();

// ...

private void vetoTextFieldChanges() {
    String newText = textField.getText();
    if (!newText.matches("\\d*")) {
        textField.setText(newText.replaceAll("[^\\d]", ""));
    }

    if (newText.isEmpty())
        textField.setText("0");
}

Note that the whole approach of watching for changes and then modifying them if they are inconsistent with your business logic is not very satisfactory. For example, if you have other listeners registered with the property, they may see the intermediate (invalid) values of the text. The supported way to do this is to use a TextFormatter. The TextFormatter both allows you to veto changes that are requested to the text, before the textProperty() is updated, and to convert the text to an appropriate value. So in your case you could do:

UnaryOperator<TextFormatter.Change> filter = change -> {

    // remove any non-digit characters from inserted text:
    if (! change.getText().matches("\\d*")) {
        change.setText(change.getText().replaceAll("[^\\d]", ""));
    }

    // if new text is empty, replace all text with "0":
    if (change.getControlNewText().isEmpty()) {
        change.setRange(0, change.getControlText().length());
        change.setText("0");
    }

    return change ;
};

TextFormatter<Integer> formatter = new TextFormatter<Integer>(new IntegerStringConverter(), 0, filter);
textField.setTextFormatter(formatter);

Now you can use the formatter to get the (numeric) values directly via the formatter's value property, and bind them to your model, etc:

// assume model is a data model and valueProperty() returns a Property<Integer>:
formatter.valueProperty().bindBidirectional(model.valueProperty());

Note that a binding like this will both update the formatter (and consequently the text field) when the model changes, and will also initialize it depending on your model value (thus providing a solution for your original question).