Search code examples
javajavafxjavabeans

Binding label text to an observable variable


Just for background, I used to code a bit years ago doing web design stuff, but for all intents and purposes I am just getting my feet wet with Java (like literally 3 days in), so try and take it easy on me! This whole program just tracks clicks, saves and loads them, and that's basically all it will ever do. It's just an exercise and it has proven to be plenty for me to chew on for the last several days.

The goal is to get the label in Click.fxml to change whenever the click count increases. I've got the logic working correctly which I've verified through the console, but I can't seem to get this label to update when the count value increases. I already know I've overlooked or missed something incredibly simple or otherwise just have a fundamental misunderstanding of how any of this works!

Here's what I have at the moment, based on reading through lots of stack questions and documentation:

Click.fxml

<?xml version="1.0" encoding="UTF-8"?>
<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<VBox
  xmlns="http://javafx.com/javafx/8"
  xmlns:fx="http://javafx.com/fxml"
      fx:controller="application.ViewModel">
  <Button text="Click 1x" onAction="#clicked1x" />
  <Button text="Click 5x" onAction="#clicked5x" />
  <Button text="Test" onAction="#test" />
  <Label fx:id="currentTotalLabel"/>
</VBox>

ViewModel.java

package application;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class ViewModel {
  public Counter counter = new Counter();
  public Label currentTotalLabel;
  public ObservableValue<? extends String> totalText =
      new SimpleStringProperty(this, "rand", getCurrentTotalText());

  public void initialize() {
    currentTotalLabel.textProperty().bind(totalText);

    totalText.addListener(new ChangeListener<String>() {
      public void changed(ObservableValue<? extends String> observable,
          String oldValue, String newValue) {
        System.out.println("changed: " + oldValue + " -> " + newValue);
      }
    });
  }

  @FXML
  private void clicked1x(ActionEvent event) {
    counter.setTotal(1);
    System.out.println("Clicked 1x; " + getCurrentTotalText());
  }

  @FXML
  private void clicked5x(ActionEvent event) {
    counter.setTotal(5);
    System.out.println("Clicked 5x; " + getCurrentTotalText());
  }

  @FXML
  public String getCurrentTotalText() {
    String currentTotalText = counter.getTotal().getValue() + " total clicks!";
    return currentTotalText;
  }

  @FXML
  private void test(ActionEvent event) {
    System.out.println(getCurrentTotalText());
  }
}

I've tried a bunch of other ways to approach this, and I purposely want to achieve this using binding. I just don't know exactly where I've gone wrong.


Solution

  • The issue with the code you shared is that the totalText property in ViewModel is not being updated when the Counter value changes, and Label is not bound to the totalTextProperty.

    To fix it you could try :

    package application;
    
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.event.ActionEvent;
    import javafx.fxml.FXML;
    import javafx.scene.control.Label;
    
    public class ViewModel {
    
        private final Counter counter = new Counter();
        private final StringProperty totalText = new SimpleStringProperty();
    
        @FXML
        private Label currentTotalLabel;
    
        public void initialize() {
            totalText.set(getCurrentTotalText());
            currentTotalLabel.textProperty().bind(totalText);
    
            counter.totalProperty().addListener((observable, oldValue, newValue) -> {
                totalText.set(getCurrentTotalText());
            });
        }
    
        @FXML
        private void clicked1x(ActionEvent event) {
            counter.setTotal(1);
            System.out.println("Clicked 1x; " + getCurrentTotalText());
        }
    
        @FXML
        private void clicked5x(ActionEvent event) {
            counter.setTotal(5);
            System.out.println("Clicked 5x; " + getCurrentTotalText());
        }
    
        @FXML
        public String getCurrentTotalText() {
            return counter.getTotal() + " total clicks!";
        }
    
        @FXML
        private void test(ActionEvent event) {
            System.out.println(getCurrentTotalText());
        }
    }
    

    And for counter you could use :

    package application;
    
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    
    public class Counter {
        private final IntegerProperty total = new SimpleIntegerProperty(0);
    
        public IntegerProperty totalProperty() {
            return total;
        }
    
        public int getTotal() {
            return total.get();
        }
    
        public void setTotal(int value) {
            total.set(getTotal() + value);
        }
    }