JavaFX: Binding does not work with included FXML Code Answer

Hello Developer, Hope you guys are doing great. Today at Tutorial Guruji Official website, we are sharing the answer of JavaFX: Binding does not work with included FXML without wasting too much if your time.

The question is published on by Tutorial Guruji team.

I want my included/embedded fxml-based controls to inform their containing objects (parent views) about changes in their states/properties. As you can see below I wrote a Container.fxml including a View.fxml plus the according to controller classes, named “Container.java” and “View.java”.

When the textField of ViewController.java changes, its StringProperty ‘textProperty_View’ is updated. That seems to be working properly as the OnAction-handler is invoked as expected.

But the listener in the parent ContainerController.java does not fire though I bound its StringProperty to the StringProperty of ViewController.java and added a ChangeListener for this property.

What am I doing wrong?

P.S.:

  • I simplified the example by doing the same thing without fx:include to see if the binding works. It works. But not when I embed the view like in the described problem (see code below)
  • Am using javafx-15-ea+3 and java11

AppStarter.java

public class AppStarter extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("/fxml/Container.fxml"));
        Scene scene = new Scene(root, 300, 275);
        stage.setScene(scene);
        stage.show();
    }
}

ContainerController.fxml

<HBox xmlns="http://javafx.com/javafx/10.0.2-internal" 
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="gui.ContainerController">

      <fx:include source="View.fxml" fx:id="view"/>

<!-- <TextField fx:id="textFieldMain" prefWidth="200" onAction="#onActionMain"/>  -->

</HBox>

ContainerController.java

public class ContainerController implements Initializable {

    private final StringProperty textProperty_Main = new SimpleStringProperty();

    @FXML
    private ViewController viewController;

//    @FXML
//    void onActionMain(ActionEvent event) {
//        System.out.println("onActionMain " + viewController.getTextProperty_View());
//    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        textProperty_Main.bind(viewController.textProperty_ViewProperty());
        textProperty_MainProperty().addListener((observable, oldValue, newValue) ->
                System.out.println("textProperty of Container changed to: " + getTextProperty_Main())
        );
    }

    public String getTextProperty_Main() {
        return textProperty_Main.get();
    }

    public StringProperty textProperty_MainProperty() {
        return textProperty_Main;
    }
}

View.fxml

<HBox xmlns="http://javafx.com/javafx" 
      xmlns:fx="http://javafx.com/fxml" 
      fx:controller="gui.ViewController">

     <TextField fx:id="textField" prefWidth="200" onAction="#onAction">Unset</TextField>

</HBox>

ViewController.java

public class ViewController {

    private final StringProperty textProperty_View = new SimpleStringProperty();

    @FXML
    private TextField textField;

    @FXML
    void onAction(ActionEvent event) {
        textProperty_ViewProperty().setValue(textField.getText());
        System.out.println("textProperty of View changed to: " + textProperty_ViewProperty().getValue());
    }

    public String getTextProperty_View() {
        return textProperty_View.get();
    }

    public StringProperty textProperty_ViewProperty() {
        return textProperty_View;
    }
}

Answer

The issue you are seeing is a really frustrating one: JavaFX bindings use WeakListeners to implement the bindings. This means that if the bindings go out of scope, the listeners are eligible for garbage collection, and consequently may stop working. (In my setup, your code actually works, but if I invoke System.gc() somewhere, it immediately stops working.)

The only fix I can find for this is to explicitly retain a reference to the ContainerController in the application class (since the ContainerController has a reference to the ViewController, and the ViewController has a reference to the text field, this creates a path to the binding’s listener via the listeners on the text field, etc.):

public class AppStarter extends Application {
    
    private ContainerController controller ;

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

    @Override
    public void start(Stage stage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("Container.fxml"));
        Parent root = loader.load();
        this.controller = loader.getController();
        Scene scene = new Scene(root, 300, 275);
        stage.setScene(scene);
        stage.show();
    }
}
We are here to answer your question about JavaFX: Binding does not work with included FXML - If you find the proper solution, please don't forgot to share this with your team members.

Related Posts

Tutorial Guruji