3

Currently I am learning how to use Bindings and Binding-Events properly. I already read a chapter of a book about it and in general I have no problem in using Bindings.

For testing my knowledge, I wrote a little JavaFX8 Application. I got 2 TextFields, but at the moment I am focusing on one TextField, called "firstName". I am using a BooleanBinding. Whenever the TextField is getting filled, the BooleanBinding is set to "true". If there's no Input in the Field, the BooleanBinding is set to "false". My goal is to update Label called "statusLabel", whenever the BooleanBinding got changed.

This is how the binding looks:

BooleanBinding nameEntered = firstName.textProperty().isNotEmpty();

This is my ChangeListener:

nameEntered.addListener((o, oldValue, newValue) -> {
        statusLabel.setText(newValue.toString());
});

For a short amount of time, the Listener is working properly. When the BooleanBinding got changed, the Label is getting updated. But after some input changes (deleting the input, filling again etc...) the Label isn't getting updated anymore. Any ideas how to fix this?

Here is the full code:

FXMLController:

package gui;

/*
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.binding.When;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

 */
    public class LayoutController implements Initializable {

    /**
     * Initializes the controller class.
     */
      @FXML
      private TextField firstName;

      @FXML
      private TextField secondName;

      @FXML
      private CheckBox checkBox1;

      @FXML
      private CheckBox checkBox2;

      @FXML
      private CheckBox checkBox3;

      @FXML
      private Label statusLabel;

      @Override
      public void initialize(URL url, ResourceBundle rb) {
        BooleanBinding nameEntered = firstName.textProperty().isNotEmpty();
        nameEntered.addListener((o, oldValue, newValue) -> {
            statusLabel.setText(newValue.toString());
        });
      }
}

MainView.java:

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

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author xyz
 */
public class MainView extends Application{

    @Override
    public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("Layout.fxml"));
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Benutzerauswahl");
        primaryStage.show();
    }

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

FXMLLayout:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>


<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="310.0" prefWidth="343.0"  xmlns:fx="http://javafx.com/fxml/1" fx:controller="gui.LayoutController">
   <center>
      <AnchorPane prefHeight="371.0" prefWidth="380.0" BorderPane.alignment="CENTER">
         <children>
            <GridPane layoutX="50.0" layoutY="103.0" prefHeight="234.0" prefWidth="281.0" AnchorPane.leftAnchor="50.0" AnchorPane.rightAnchor="49.0" AnchorPane.topAnchor="50.0">
              <columnConstraints>
                <ColumnConstraints hgrow="SOMETIMES" maxWidth="155.0" minWidth="10.0" prefWidth="110.0" />
                <ColumnConstraints hgrow="SOMETIMES" maxWidth="197.0" minWidth="10.0" prefWidth="171.0" />
              </columnConstraints>
              <rowConstraints>
                <RowConstraints maxHeight="40.0" minHeight="10.0" prefHeight="40.0" vgrow="SOMETIMES" />
                <RowConstraints maxHeight="40.0" minHeight="10.0" prefHeight="40.0" vgrow="SOMETIMES" />
                <RowConstraints maxHeight="400.0" minHeight="10.0" prefHeight="88.0" vgrow="SOMETIMES" />
              </rowConstraints>
               <children>
                  <Label prefHeight="17.0" prefWidth="56.0" text="Vorname:" GridPane.halignment="CENTER" />
                  <Label prefHeight="17.0" prefWidth="69.0" text="Nachname:" GridPane.halignment="CENTER" GridPane.rowIndex="1" />
                  <TextField fx:id="firstName" prefHeight="25.0" prefWidth="144.0" GridPane.columnIndex="1" />
                  <TextField fx:id="secondName" prefHeight="25.0" prefWidth="144.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
                  <AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="2">
                     <children>
                        <CheckBox fx:id="checkBox1" layoutX="14.0" layoutY="42.0" mnemonicParsing="false" text="Kurs 1" />
                        <CheckBox fx:id="checkBox2" layoutX="14.0" layoutY="69.0" mnemonicParsing="false" text="Kurs 2" />
                        <CheckBox fx:id="checkBox3" layoutX="14.0" layoutY="96.0" mnemonicParsing="false" text="Kurs 3" />
                        <Label fx:id="statusLabel" layoutX="43.0" layoutY="132.0" prefHeight="17.0" prefWidth="84.0" text="Status" />
                     </children>
                  </AnchorPane>
               </children>
            </GridPane>
         </children>
      </AnchorPane>
   </center>
</BorderPane>
Tjatte
  • 251
  • 4
  • 14
  • I read the duplicate but this solution isnt working for me. Even if i retain the reference to nameEntered, the binding is still not working after a few secs. – Tjatte Sep 06 '16 at 13:34
  • 2
    You can use a simple binding like: `statusLabel.textProperty().bind(firstName.textProperty().isNotEmpty().asString());` – DVarga Sep 06 '16 at 13:35
  • 1
    Forcing retention of the binding is a bit tricky here. The code from @DVarga will do it, with `statusLabel.textProperty().bind(nameEntered.asString());`, because the `statusLabel` is retained, and this makes it (indirectly) retain a reference to your binding. Making the binding an instance variable in the controller alone doesn't work though, because you have no live reference to the controller: you can force it by *also* creating an instance variable for the controller in `MainView` and initializing that when you load the FXML... which is really a pain... – James_D Sep 06 '16 at 13:40
  • Thanks for your help, i really appreciate it. You are right, that solution is not the best, so i better use the bind of DVarga. – Tjatte Sep 06 '16 at 13:41
  • @James_D, can you please show me a little code snipet for the last solution? (Creating an instance variable..) Just for better understanding.. Would be very nice, thanks. :) – Tjatte Sep 06 '16 at 13:56

1 Answers1

5

This is really a duplicate of JavaFX Beans Binding suddenly stops working: the problem is that the binding is getting "prematurely garbage collected" because there are no live references to it that are retained. Forcing the reference to be retained in a controller seems to be a little tricky.

First note that if you actually bind a property of a UI element (which is necessarily in scope as long as it is displayed), then the UI element indirectly keeps a reference to the binding. Consequently in your code this will fix the problem:

statusLabel.textProperty().bind(nameEntered.asString());

(instead of the listener you currently have). If you can't actually use a binding, then it seems that first you need to get the controller to retain a reference to the binding:

public class LayoutController implements Initializable {

    // existing code...

    private BooleanBinding nameEntered ;

    public void initialize(URL url, ResourceBundle rb) {
        nameEntered = firstName.textProperty().isNotEmpty();
        nameEntered.addListener((o, oldValue, newValue) -> {
            statusLabel.setText(newValue.toString());
        });
    }

}

and then additionally you need to force an reference to the controller itself to stay in scope:

public class MainView extends Application{

    private LayoutController controller ;

    @Override
    public void start(Stage primaryStage) throws Exception {
        FMXLLoader loader = new FXMLLoader(getClass().getResource("Layout.fxml"));
        Parent root = loader.load();
        controller = loader.getController();
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Benutzerauswahl");
        primaryStage.show();
    }

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

There may be a more obvious way to do this, but I can't find one that works.

Community
  • 1
  • 1
James_D
  • 201,275
  • 16
  • 291
  • 322