Python Cheese
Python Cheese

Reputation: 157

JavaFX: Have background task update label text on FXML window?

My goal is to create a javaFX window that, when clicked, will increment the counter of a singleton class. A background task should read the value in the singleton and display it on the same window. Below is the singleton.

Singleton.java:

public class Singleton {
    private static Singleton singleton = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return singleton;
    }

    //=====================================

    private int count = 0;

    public void increment() {
        count++;
        System.out.println(count);
    }

    public int getCount() {
        return count;
    }

}

To create the background thread, I've tried creating a task through the initialize() method provided by JavaFX.INITIALIZABLE, but this creates a "Not on FX application thread" error. Below is the main which grabs the FXML and starts the window.

main.java:

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
            primaryStage.setTitle("Hello World");

            Scene scene = new Scene(root,400,400);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();

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

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

Sample.fxml - Here is where I define window structure. Note the fx:id="counter" for the first label.

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="199.0" prefWidth="230.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
   <children>
      <AnchorPane layoutX="22.0" layoutY="9.0" prefHeight="182.0" prefWidth="186.0">
         <children>
            <Button layoutX="14.0" layoutY="76.0" mnemonicParsing="false" onAction="#buttonClicked" text="Increment" />
            <Label fx:id="counter" layoutX="131.0" layoutY="81.0" text="0" />
            <Label layoutX="28.0" layoutY="34.0" text="First JavaFX Project" />
         </children>
      </AnchorPane>
   </children>
</Pane>

Controller.java - And finally the controller. The initialize function creates a new thread to run the task needed to get the counter value, but it can't edit the original window.

public class Controller implements Initializable {

    // Links to Singleton classes
    private Singleton sin = Singleton.getInstance();


    // Links to FXML elements
    @FXML
    private Label counter;

    public void buttonClicked() {
        System.out.print("Click");
        sin.increment();
    }

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

        MyTask myTask = new MyTask();
        Thread myTaskThread = new Thread(myTask);
        myTaskThread.start();
    }

    class MyTask extends Task<Void>{

        @Override
        protected Void call() throws Exception {
            while(true) {
                int count = sin.getCount();
                counter.setText(Integer.toString(count));


            }
        }

    }
}

How can I get this thread to periodically update the FXML elements on the running application?

Upvotes: 0

Views: 740

Answers (2)

user11068159
user11068159

Reputation:

I believe your line trying to update the text must be used inside this code

Platform.runLater(() -> {insert your code here});

This is what your class MyTask should look like when updating the text value:

class MyTask extends Task<Void>{

    @Override
    protected Void call() throws Exception {
        while(true) {
            int count = sin.getCount();
            Platform.runLater(() -> {
               counter.setText(Integer.toString(count));
            });
        }
    }

}

When trying to update anything on the user interface, you must be on the application thread. The code above tells the program to execute the code on the application thread.

Upvotes: 0

mehdi maick
mehdi maick

Reputation: 325

Your use case does not need a background thread to be accomplished, you simply need to use the javafx two way binding, and StringProperty


public class Controller implements Initializable {

  // Links to Singleton classes
  private Singleton sin = Singleton.getInstance();

  // Links to FXML elements
  @FXML
  private Label counter;

  private StringProperty singletonCounter;

  @Override
  public void initialize(URL url, ResourceBundle resourceBundle) {
    singletonCounter = new SimpleStringProperty();
    singletonCounter.set(sin.getCount() + "");
    counter.textProperty().bind(singletonCounter);
  }

  public void buttonClicked() {
    sin.increment();
    singletonCounter.setValue(sin.getCount() + "");
  }

}

Upvotes: 0

Related Questions