15

I've set up a data binding between a Label in an FXML file and an IntegerProperty in the associated controller. The problem is that, while the label gets set to the correct value upon initialization, it is not updating when the property's value changes.

FXML file

<?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.*?>

<GridPane xmlns:fx="http://javafx.com/fxml"
   fx:controller="application.PaneController" minWidth="200">
   <Label id="counterLabel" text="${controller.counter}" />
   <Button translateX="50" text="Subtract 1"
      onAction="#handleStartButtonAction" />
</GridPane>

Controller

package application;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;

public class PaneController implements Initializable
{
    private IntegerProperty counter;

    public int getCounter()
    {
        return counter.get();
    }

    public void setCounter(int value)
    {
        counter.set(value);
    }

    public PaneController()
    {
        counter = new SimpleIntegerProperty(15);
    }

    @Override
    public void initialize(URL url, ResourceBundle resources)
    {
    }

    @FXML
    private void handleStartButtonAction(ActionEvent event)
    {
        setCounter(getCounter() - 1);
        System.out.println(getCounter());
    }
}

Expectation

Each time I push the "Subtract 1" button, the counter will decrement by 1, and the counterLabel will update automatically.

Reality

The counter does decrement by 1, but the counterLabel remains stuck at 15 (the initial value).

Question

I was under the impression (e.g., from this forum post) that what I've done should work. What am I missing?

Aerospace
  • 1,270
  • 12
  • 19
devuxer
  • 41,681
  • 47
  • 180
  • 292

1 Answers1

40

You need to add a JavaFX specific accessor variableNameProperty to the controller:

public IntegerProperty counterProperty() {
    return counter;
}

EDIT: More details.
The API documentation mentions not so much about this JavaFX's JavaBeans architecture. Just an introduction about it here (Using JavaFX Properties and Binding) but again nothing about its necessity.

So lets dig some source code! :)
Straightforwardly, we start to look into FXMLLoader code first. We notice prefix for binding expression as

public static final String BINDING_EXPRESSION_PREFIX = "${";

Further at line 279 FXMLLoader determines if (isBindingExpression(value)) then to create binding, instantiates BeanAdapter and gets propertyModel:

BeanAdapter targetAdapter = new BeanAdapter(this.value);
ObservableValue<Object> propertyModel = targetAdapter.getPropertyModel(attribute.name);

If we look into BeanAdapter#getPropertyModel(),

public <T> ObservableValue<T> getPropertyModel(String key) {
    if (key == null) {
        throw new NullPointerException();
    }
    return (ObservableValue<T>)get(key + BeanAdapter.PROPERTY_SUFFIX);
}

it delegates to BeanAdapter#get() after appending String PROPERTY_SUFFIX = "Property";
In the get() method, simply the getter (either counterProperty or getCounter or isCounter) invoked by reflection, returning the result back. If the getter does not exist null returned back. In other words, if the "counterProperty()" does not exist in JavaBean, null returned back in our case. In this case the binding is not performed due to the statement if (propertyModel instanceof Property<?>) in FXMLLoader. As a result, no "counterProperty()" method no bindings.

What happens if the getter is not defined in the bean? Again from the BeanAdapter#get() code we can say that if the "getCounter()" cannot be found the null returned back and the caller just ignores it as no-op imo.

Uluk Biy
  • 48,655
  • 13
  • 146
  • 153
  • I tried that before, but it didn't work because I also changed my FXML markup to `${controller.counterProperty}`. If I leave it as just `${controller.counter}`, it works. Thanks very much! – devuxer Nov 07 '13 at 02:26
  • 2
    Also, interestingly, it fails if I remove the definitions to `getCounter()` and `setCounter()` (and just work with the `counter` field directly). Does this mean getters/setters on the controller are used by the binding mechanism? (It's unexpected that I would need to provide a getter for the *property* as well as a getter/setter for the *value* of the property.) – devuxer Nov 07 '13 at 02:32
  • Thanks for that update, Uluk! Definitely helps to understand the system better. I would give you another +1 if I could. – devuxer Nov 07 '13 at 20:00
  • 1
    Hi thank you very much for your answers. In my FXML file in intellij the controller keyword is red showing the error message `Cannot resolve symbol 'controller'`. when i run it - everthing works fine though. seems like an intellij integration failure. Someone knows how to fix this? – JuHwon May 25 '15 at 20:32
  • @JuHwon, I am not on intellij. So the only guess is you may forgotten the "fx" prefix, namely it should be "fx:controller". – Uluk Biy May 26 '15 at 05:03
  • Adobe/Apache Flex does it better. I wonder, couldn't we just have an @ Property or @ Binding annotation which cuts down on the boilerplate? But I guess this will do... – User Jan 25 '17 at 18:26
  • 1
    I think my unconscious is not happy with me experimenting with JavaFX: for a moment, I read `BeanAdapter#get()` as `BeanAdapter.regret()`... – tevemadar Oct 17 '19 at 11:01