JavaFX-Weird behavior in bidirectional binding

I am trying to bind a TextArea’s textProperty to a StringProperty in controller’s initialize() method.

And both of them are listened by listeners to perform some behavior when value changes.

But something weird happens.

I build a simple model to reproduce the situation.

Main.java

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setScene(new Scene(root, 400, 300));
        primaryStage.show();
    }


    public static void main(String[] args) {
        launch(args);
    }
}

sample.fxml

<?import javafx.scene.layout.GridPane?>

<?import javafx.scene.control.TextArea?>
<GridPane fx:controller="sample.Controller"
          xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10"
          prefHeight="300" prefWidth="400">
  <TextArea fx:id="textArea"/>
</GridPane>

And I don’t think the above code is relevant to this question. But just in case, I put them here.

And here comes the Controller.

Controller.java

package sample;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;

public class Controller {

  @FXML
  TextArea textArea;

  private StringProperty toBind = new SimpleStringProperty();

  public void initialize() {
    textArea.textProperty().bindBidirectional(toBind);

    textArea.textProperty().addListener((observable, oldValue, newValue) -> {
      System.out.print("textArea: ");
      System.out.println(newValue);
    });

    toBind.addListener((observable, oldValue, newValue) -> {
      System.out.print("toBind: ");
      System.out.println(newValue);
    });
  }
}

And with this controller, when I input the sequence ‘abcd’ to the textarea, I got:

textArea: a
textArea: ab
textArea: abc
textArea: abcd

It seems that the change event for the toBind object is not fired.

So then I tried to print the value of toBind in textArea’s Listener.

The new code is:

package sample;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;

public class Controller {

  @FXML
  TextArea textArea;

  private StringProperty toBind = new SimpleStringProperty();

  public void initialize() {
    textArea.textProperty().bindBidirectional(toBind);

    textArea.textProperty().addListener((observable, oldValue, newValue) -> {
      System.out.print("textArea: ");
      System.out.println(newValue);

      // ----- New statements. -----
      System.out.print("toBind value in textArea: ");
      System.out.println(toBind.get());
      // ----- New statements. -----
    });

    toBind.addListener((observable, oldValue, newValue) -> {
      System.out.print("toBind: ");
      System.out.println(newValue);
    });
  }
}

Then I got:

toBind: a
textArea: a
toBind value in textArea: a
toBind: ab
textArea: ab
toBind value in textArea: ab
toBind: abc
textArea: abc
toBind value in textArea: abc
toBind: abcd
textArea: abcd
toBind value in textArea: abcd

Why this happened? The event is fired properly.

I am very confused and tried my best to figure this out but failed.

I’d be so appreciate if someone can help me out.