AnonymousGuy
AnonymousGuy

Reputation: 37

JAVAFX how to update GUI elements (nodes)

This question is already asked but i copuldnt udnerstand it. Imagine that. I have a program with 2 scenes. First scene1 is opened which is the scene for connection to database. There is a label called Status which should turn from "Disconnected" to "Connected" when the connection is established(by clicking the COnnect button). So i made a function to take the button "Connect" onClick event. This function is declared and defined inside a controller class (i am using fmxl designing with scene builder). So basicly i want to change the status to "COnnected" (status.setText("Connected")) from the connection function(method) which is inside the controller class. However when I do that, the text isn't changed instantly after the connection is established, but it changes when the scene is about to close and i am about to change the scene to the new one... I read on the internet and i saw that i should use Platform.runLater and threading so i tried:

private void changeSC() throws IOException, InterruptedException, SQLException
{

    dbConnect();

    Thread thrd = new Thread() {

        public void run() {

                 Platform.runLater(new Runnable() {
                     @Override public void run() {
                     status.setText("Connected");
                        status.setTextFill(Color.GREEN);
                     }});
        }
    };
    thrd.start();

    //pb.setProgress(1.0);
    Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
    Scene primary = new Scene(root,1024,768);
    primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
    System.out.println("Text changing to COnnected");
    status.setTextFill(Color.GREEN);
    Thread.sleep(2000);
    Main.window.setScene(primary);

}

changeSC is the function that is executed when Connect button is clicked. This is my old version which also doesnt work:

private void changeSC() throws IOException, InterruptedException, SQLException
{

    dbConnect();

      status.setText("Connected");
      status.setTextFill(Color.GREEN);


    //pb.setProgress(1.0);
    Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
    Scene primary = new Scene(root,1024,768);
    primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
    System.out.println("Text changing to COnnected");
    status.setTextFill(Color.GREEN);
    Thread.sleep(2000);
    Main.window.setScene(primary);

}

The problem is with the text which should change to "Connected". It changes just when my scene is about to be switched....

Upvotes: 4

Views: 16562

Answers (3)

leobelizquierdo
leobelizquierdo

Reputation: 1668

Since you are connecting to a database, you should put this code to run in background using Task or a Service to keep the GUI Thread responding to the user inputs. Just remember that only in the GUI Thread you can update the view state (changing the value of a text in your case). You can use a java Thread and use Platform.runLater which means that the code inside is schedule to be precessed by the GUI Thread but in your case you are using in the wrong way. First the logic to connect to the database should be inside the method run of the thread and once the method finish, set the value of the text and do whatever you want after. Also you'll want to show the new Scene when all the process has been finished to get a chance to the user to see the change in the text. You can change your code in this way:

private void changeSC() throws IOException, InterruptedException, SQLException
{
  Thread thrd = new Thread() {

    public void run() {
        dbConnect();
        Platform.runLater(new Runnable() {
             @Override public void run() {
                 status.setText("Connected");
                 status.setTextFill(Color.GREEN);
                 //pb.setProgress(1.0);
                 Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
                 Scene primary = new Scene(root,1024,768);
                 primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
                 System.out.println("Text changing to COnnected");
                 status.setTextFill(Color.GREEN);
                 Main.window.setScene(primary);
              }
        });
     }
  };
  thrd.start();
}

If you choose use a Task you dont have to deal with Platform.runLater explicitly. You only need to create a task (an implementation of the class Task), wrap this inside a java Thread, start it and the set a handler for the different events (eg: setOnSucceeded). This is your code using Task:

private void changeSC() throws IOException, InterruptedException, SQLException
{  
    Task<Void> task = new Task<Void>(){
        @Overrdie
        protected Void call()  {
            dbConnect();   
            return null;
        }
    };
    //start Task
    Thread t = new Thread(task);
    t.setDaemon(true); // thread will not prevent application shutdown
    t.start();

    task.setOnSucceeded(event -> {
        Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
        Scene primary = new Scene(root,1024,768);
        primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        System.out.println("Text changing to COnnected");
        status.setTextFill(Color.GREEN);
        Main.window.setScene(primary);
    });


}

Upvotes: 0

AnonymousGuy
AnonymousGuy

Reputation: 37

ok i fixed it by setting task.SetonFailed() :)

Upvotes: -3

jns
jns

Reputation: 6952

You need to use a Task, if you have some long running operation. Otherwise when that operation is called from the JavaFX Application Thread it would block the GUI.

If you want to update the GUI from the Task you have to use Platform.runlater, which will run the code on the JavaFX Application Thread: Platform.runlater

Updates to the Nodes of your GUI have always to be performed on the JavaFx Thread.

When you update status inside the Listener of button it should work.

  button.setOnAction(evt -> {
        dbConnect();
        status.setText("Connected");
        // ...
    });

If dbConnect() takes some time, you can use a Task:

 Task<Void> longRunningTask = new Task<Void>() {

        @Override
        protected Void call() throws Exception {
            dbConnect();
            Platform.runLater(() -> status.setText("Connected"));
            return null;
        }
    };


    button.setOnAction(e -> {
        new Thread(longRunningTask).start();
    });

Upvotes: 4

Related Questions