0

I've been struggling for days over a stupid issue, and I need your help. I'm simply trying to display a indeterminate process indicator like the one below, within a separate Stage, while my main code performs a loop. If a certain condition is met while looping, the progress stage should close and the main code continues.

enter image description here

Right now I'm able to open a new stage prior to the main code starting the loop, but the progress indicator won't display in the stage yet. But once the condition is met, then the indicator suddenly appears in the stage and rotates. BUT as soon as the main code begins running again the indicator freezes, yet remains visible.

I'll briefly explain what each code below does. How do I make it so that the loading stage appears initially WITH the indicator visible, and then that stage CLOSES when the showMessage() method is called? Basically I want to show the user that background looping is happening, until the main code reaches showMessage().

Main.java I won't post it, as it only creates the FIRST GUI. It uses menu.fxml as the resource in FXMLLoader ... and menu.fxml uses Controller.java as controller.

Controller.java This code changes the scene in the Main GUI to have a new layout, which asks the user to click one of the visible buttons. It then checks if the source button is Button1... and if it is then create/show a new stage which uses sample.fxml as the resource. It then runs the final file, Loop.java

public class Controller implements Initializable {

    public Stage loadingStage;

    @FXML
    ProgressIndicator progressIndicator;

    public void handlerButtonClick() throws IOException {
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("field.fxml"));
        Parent rootOne = loader.load();
        ((Controller) loader.getController()).setPrimaryStage(primaryStage);
        ((Controller) loader.getController()).setPrimaryScene(scene);
        sceneField = new Scene(rootOne, 420, 510);
        primaryStage.setScene(sceneField);
        primaryStage.setTitle("Import Data");
        primaryStage.show();
    }

    public void fieldOption(ActionEvent e) throws Exception {

        source = e.getSource();
        if (source == Button1) {

            Loop loopObj = new Main();
            String[] args = {};

            //Create new stage to contain the Progress Indicator
            FXMLLoader loader2 = new FXMLLoader(getClass().getResource("sample.fxml"));
            Parent root2 = loader2.load();
            Controller2 controller = loader2.getController();
            loadingStage = new Stage();
            loadingStage.setTitle("Loading Stage");
            Scene scene = new Scene(root2, 200, 200);
            loadingStage.setScene(scene);
            loadingStage.show();

            //Runs the looper
            loopObj.start(stage);
        }

    }

Sample.fxml

This code creates the Progress Indicator, and uses the controller Controller2

<AnchorPane prefHeight="200.0" prefWidth="200.0" xmlns:fx="http://javafx.com/fxml/1"
            xmlns="http://javafx.com/javafx/2.2" fx:controller="Controller2">
    <children>
        <ProgressIndicator fx:id="progressIndicator" layoutX="78.0" layoutY="55.0" progress="-1.0"/>
    </children>
</AnchorPane>

Controller2.java This code implements Initializable in order to make the Progress Indicator visible:

public class Controller2 implements Initializable {
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        ProgressIndicator progressIndicator = new ProgressIndicator();
        progressIndicator.setVisible(true);
    }
}

Loop.java This is the main code that performs the loop. The loading stage should display the Progress Indicator until Loop.java calls its showMessage() function, at which point the loading stage closes. I know that it seems that one of these methods aren't necessary, but its only because they're stripped for demo purposes.

public class Loop extends Application {
    @Override
    public void start(Stage ignored) throws Exception {
        Platform.setImplicitExit(false);
        for (int i = 0; i <= 100; i++) {
            if (i == 75){
                saveAttachment("Hello");
            }
        }
    }

    public static void saveAttachment(String subject) throws IOException, MessagingException {
        showMessage(subject);
    }

    private static void showMessage(String subject) throws IOException, MessagingException {
        // Close the loading stage here.
    }
}

Notice how it uses public void start(Stage ignored). Main.java uses public void start(Stage primaryStage). Perhaps this is bad. I'm so frustrated please help!

UPDATE

I've applied Brian's suggestions, and within Controller.java have added a new task like so:

 Stage loadingStage = new Stage();

           //create task object
           Task<Void> task = new Task<Void>(){
               @Override
               protected Void call() throws Exception{
                   System.out.println("Background task started...");
                   FXMLLoader loader2 = new FXMLLoader(getClass().getResource("sample.fxml"));
                   Parent root2 = loader2.load();
                   Controller2 controller = loader2.getController();
                   loadingStage.setTitle("Hello World");
                     Scene scene = new Scene(root2, 450, 250);
                   System.out.println("HERE6");
                   loadingStage.setScene(scene);
                   System.out.println("HERE7");
                   loadingStage.show();
                   System.out.println("HERE8");
                   return null;
               }
           };

           Thread th = new Thread(task);
           th.setDaemon(true);
           System.out.println("Starting background task...");
           th.start();

The problem now is that it doesn't reach the printout line saying "HERE7". It successfully enters the task and continues the main code, but the task doesn't get beyond where it prints "HERE6" therefore no new stage is opened. What gives?

Casey B.
  • 279
  • 3
  • 13
  • You cannot update the UI on the background thread, so it throws an exception at `loadingStage.setScene(...)`. But I don't see why you are doing that part in the background thread anyway. Surely it's the `for` loop with all the calls to `saveAttachment` that takes a lot of time? Maybe read my answer to http://stackoverflow.com/questions/30249493/using-threads-to-make-database-requests for some background information. The [`Task` API docs](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html) are full of examples. – James_D Dec 13 '15 at 22:45
  • Thanks, I'm currently tinkering and reading. Will report back. – Casey B. Dec 13 '15 at 23:00
  • I've gotten to the point where the stage opens properly, but I learned that a `FutureTask` is required for this purpose. – Casey B. Dec 14 '15 at 21:33
  • Why do you need a `FutureTask`? – James_D Dec 14 '15 at 21:34
  • The task thread I've created involves a `for` loop, which contains a call to another method. This other method displays a dialog and *should* wait for user's input before continuing. Apparently background threads need special treatment in order to pause, and `futureTask` allows this by using its `get` method. Correct? I'm confused how to properly encapsulate my large code that currently resides in my `Task`, into `FutureTask`. My question took a considerable tangent, so I feel justified in creating a new, clean question. All feedback welcome. – Casey B. Dec 14 '15 at 21:57
  • For anyone having similar issues, I truly believe `FutureTask` is the way to go. See [here](http://stackoverflow.com/questions/34277951/using-futuretask-get-to-wait-for-user-input-within-background-thread) for a fresh approach at the problem. – Casey B. Dec 14 '15 at 22:41
  • If you need the background thread to wait for user input (or for something to happen on the FX Application Thread), then submitting a `FutureTask` to `Platform.runLater()` and, back on the background thread, calling `get` on the `FutureTask` is a good technique. However, note that this is a very rare use case. Typically your background thread does not need to wait for the UI thread, and many times (but certainly not every time) this is an indication you have things designed badly. – James_D Dec 14 '15 at 22:52

1 Answers1

2

In Ctlr2 you have ProgressIndicator progressIndicator = new ProgressIndicator(); but if it's in the FXML file you don't create a new one. Just do @FXML ProgressIndicator progressIndicator; like you have in the first Ctlr for some reason??

But this won't solve your problem, you're running everything on the JavaFX Application thread (GUI thread). What is probably happening (I didn't really read the code) is that you're doing both things on the same thread and the progress indicator has to wait for main to finish and then it can update the GUI. You put tags for task and concurrency, that's what you need.

Check here and do a search for more examples. https://stackoverflow.com/a/22847734/2855515

Community
  • 1
  • 1
brian
  • 10,619
  • 4
  • 21
  • 79
  • Thank you Brian, I'm on the right track now. I've updated my question (see very bottom). It successfully enters the task, but it won't create or show the new stage. It simply hangs after writing "Background task started", while the main code continues. What's wrong? – Casey B. Dec 13 '15 at 22:24
  • Actually, it won't get beyond where it says "HERE6". For some reason the line `loadingStage.setScene(scene);` is causing it to stop. – Casey B. Dec 13 '15 at 22:32