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

  • You can bind a label textProperty to a counting integer property using asString() to get a string binding.

    Label label = new Label();
    IntegerProperty count = new SimpleIntegerProperty(0);
    
    label.textProperty().bind(
            count.asString()
    );
    

    Whenever the count property is changed, the text property of the label will change to the string representation of the count value.

    Example Code

    import javafx.application.Platform;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.scene.control.Label;
    
    public class BoundLabel {
    
        public static void main(String[] args) {
            Platform.startup(BoundLabel::runTest);
            Platform.exit();
        }
    
        private static void runTest() {
            Label label = new Label();
            IntegerProperty count = new SimpleIntegerProperty(0);
    
            label.textProperty().bind(
                    count.asString()
            );
    
            label.textProperty().subscribe(labelText ->
                    System.out.println(labelText)
            );
    
            for (int i = 0; i < 5; i++) {
                count.set(count.getValue() + 1);
            }
        }
    }
    

    Outputs

    0
    1
    2
    3
    4
    5