4

I am learning JavaFX and I made a demo app that has a Label detects a value change and change its display. I tried to bind the Label.textProperty to the value but stil does not work. Here is my code:

public class MainApp extends Application {

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

private Temp aTemp = new Temp();

@Override
public void start(Stage primaryStage) {

    try {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("Sample.fxml"));      
        BorderPane root = (BorderPane)loader.load();
        SampleController sampleController = loader.getController();

        Scene scene = new Scene(root,600,600);
        scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());

        primaryStage.setScene(scene);
        primaryStage.show();

        sampleController.setModel(aTemp);

    } catch(Exception e) {
        e.printStackTrace();
    }
}}

And here is the model

public class Temp {
private StringProperty temp = new SimpleStringProperty("a");

private final ExecutorService service = Executors.newCachedThreadPool();

public Temp() {

    task.startConnection();
    service.submit(new Task<Integer>() {

        @Override
        public Integer call() {
            while(true) {
                try {
                    Thread.sleep(1000);
                }catch(Exception e) {
                    e.printStackTrace();
                }

                setTemp(getTemp() + "b");
                System.out.println(getTemp());

            }
        }
    });
}

public StringProperty getProperty() {
    return this.temp;
}

public String getTemp() {
    return this.temp.get();
}

public void setTemp(String value) {
    this.temp.set(value);
}

And then the controller

public class SampleController implements Initializable {

private Temp aTemp;

@FXML private Label tempLabel;


@Override
public void initialize(URL arg0, ResourceBundle arg1) {


}

public void setModel(Temp aTemp) {
    this.aTemp = aTemp;

    Platform.runLater(new Runnable() {
        @Override
        public void run() {
            tempLabel.textProperty().bind(aTemp.getProperty());
        }
    });

}}

What I got is that Label is change to "a" but wont change after and this exception:

Exception in thread "pool-2-thread-1" 
java.lang.IllegalStateException: Not on FX application thread; currentThread = pool-2-thread-1
chb
  • 1,727
  • 7
  • 25
  • 47
Lancehhh
  • 115
  • 1
  • 8
  • At which line do you get the exception? – steven35 Mar 20 '19 at 09:21
  • 2
    you have to wrap the setting of temp's value in Platform.runlater: because the label's text is bound to that property, you effectively change a property of a node off the fx app thread (which is not allowed, as the error tells you :) – kleopatra Mar 20 '19 at 09:27
  • @steven35 at gui.Temp.setTemp(this.temp.set(value);) and at gui.Temp$1.call(which is setTemp(getTemp() + "b");) – Lancehhh Mar 20 '19 at 09:30
  • @kleopatra is correct; alternatively, you can invoke `update value()` in your `Task` and listen for changes on the FX application thread. – trashgod Mar 20 '19 at 09:33
  • @kleopatra does it mean that I can only change temp's value inside Platform.runlater? how can I do it in a seprate Model class? – Lancehhh Mar 20 '19 at 09:34
  • @trashgod I dont quite understand "listen for changes on FX application thread"... can you please show me an example of that? – Lancehhh Mar 20 '19 at 09:39
  • When I tried Platform.runLater(new Runnable() { @Override public void run() { setTemp(getTemp() + "b"); } }); the application window does not show up and seems the while loop stopped on that – Lancehhh Mar 20 '19 at 09:45
  • @Lancehhh: for [example](https://stackoverflow.com/a/44056730/230513). – trashgod Mar 20 '19 at 09:58
  • you can edit the question to add code snippets (they are hard to read in comments :) No, you can change temp (== model value) in any thread: if you want to keep the model completely unaware of the UI, then don't bind it directly to a property of a control. Instead, on the UI side have some listener to the model property that changes the control property on the fx thread – kleopatra Mar 20 '19 at 10:15

1 Answers1

6

To flesh out my comment: if you want to keep the model completely unaware of the UI, then don't bind it directly to a property of a control. Instead, on the UI side have some listener to the model property that changes the control property on the fx thread.

A code snippet (not tested, just copied and adjusted - so might not even compile ;):

public void setModel(Temp aTemp) {
    this.aTemp = aTemp;

    aTemp.tempProperty().addListener(this::updateLabelText) 

}}

private void updateLabelText() {
   Platform.runLater(() -> label.setText(aTemp.getTemp()));  
} 
kleopatra
  • 51,061
  • 28
  • 99
  • 211