Search code examples
javafxmodel-view-controllerfxml

Can't change bound label property in MVC structured JavaFX application


I'm developing a desktop login GUI currently, and I trying to change a label's visibility while a boolean value isLoggedIn is changed. I simply reproduce my problem with intelliJ's javaFX sample project:

view: hello-view.fxml

A simple GUI with a label and a button.

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>

<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/18" fx:controller="com.example.bind_test.HelloController">
    <padding>
        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
    </padding>

    <Label fx:id="welcomeText" text="SAMPLE TEXT" />
    <Button onAction="#onHelloButtonClick" text="Hello!" />
</VBox>

controller: HelloController.java

I bind the label's visibleProperty to model's member virable loggedIn, so I except the label will be invisible since initialized, and change while user clicked the button. But the label just display as "SAMPLE TEXT" and didn't react to any event.

package com.example.bind_test;

import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class HelloController {
    @FXML
    private Label welcomeText;

    HelloModel model;

    @FXML
    public void initialize() {
        welcomeText = new Label();
        model = new HelloModel(false);

        welcomeText.setText("SAMPLE TEXT");
        welcomeText.visibleProperty().bind(model.loggedInProperty());
        model.setLoggedIn(false);
        // this label should be invisible after initialization

        System.out.println("logged in:" + model.loggedInProperty());
    }

    @FXML
    public void onHelloButtonClick() {
        welcomeText.setText("TEXT Changed");
        model.setLoggedIn(true);
        System.out.println("logged in:" + model.loggedInProperty());
    }
}

model: HelloModel.java

A model class base on MVC structure,

make the application can pass loggedIn virable between scenes.


package com.example.bind_test;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;

public class HelloModel {
private BooleanProperty LoggedIn;

    public boolean isLoggedIn() {
        return LoggedIn.get();
    }
    
    public BooleanProperty loggedInProperty() {
        return LoggedIn;
    }
    
    public void setLoggedIn(boolean loggedIn) {
        this.LoggedIn.set(loggedIn);
    
    }
    
    public HelloModel() {
        LoggedIn = new SimpleBooleanProperty(false);
    }
    
    public HelloModel(boolean loggedIn) {
        LoggedIn = new SimpleBooleanProperty(loggedIn);
    }

}

I'm confusing because the text label didn't react no matter how I changed virable with the button.

Console

logged in:BooleanProperty [value: false] // Running main()
logged in:BooleanProperty [value: true]  // After clicking HelloButton

Does the way I binding properties wrong?


Solution

  • You create a Label in your FXML file, and give it an fx:id:

    <Label fx:id="welcomeText" text="SAMPLE TEXT" />
    

    and you initially inject that label into a field in the controller:

    @FXML
    private Label welcomeText;
    

    But then in the initalize() method you replace that field with a new label, which isn't part of the UI that is displayed:

        welcomeText = new Label();
    

    Consequently, everything else you do with welcomeText (binding its visibility, changing its text, etc) operates on that new label you created, instead of the one defined in the FXML file and displayed in the scene.

    Just remove the offending line:

        // welcomeText = new Label();
    

    It is always a mistake to initialize fields annotated @FXML. The annotation means they will be initialized by the FXMLLoader.