Search code examples
javaswingswingworkerswingutilities

Java Swing Wait message will not show up


I have a Java3D scenegraph update task which takes 3-4 seconds on my laptop to complete. During this time I want a window showing up, asking the user to wait until the job completes. I have a simelar situation which takes longer and my implementation with a SwingWorker works fine. But when I apply the code to this update operation the dialog doesn't show up at all.

Here is the WaitDialog which I want to show (I also tried to inherit from JDialog - no change)

public class WaitDialog extends JFrame {
    
    private static final long serialVersionUID = 1L;            
    
    protected int rows = 2;
    protected int cols= 1;
    protected int rowSpace= 12;
    protected int colSpace= 12;
    
    public WaitDialog(Component parent, String message) {
        JPanel panel = new JPanel(new GridLayout(rows, cols, rowSpace, colSpace));      
        panel.setBorder( new EmptyBorder(12, 12, 12, 12) );                                                     
        panel.add(new JLabel(message), BorderLayout.CENTER);                                                    
        JProgressBar progressBar = new JProgressBar();                                                          
        progressBar.setIndeterminate(true);                                                                     
        panel.add(progressBar);                                                                                 
        setUndecorated(true);                                                                                   
        setAlwaysOnTop(true);
        getContentPane().add(panel);                                                                            
        pack();                                                                                                 
        setLocationRelativeTo(parent);                                                                          
        setVisible(true);                                                                                       
    }
    
    public void close() {
        setVisible(false);                                                                                      
        dispose();                                                                                              
    }
}

The method that gives me headache looks like this...

public Result doIt(String fileName) throws Exception {
        
        WaitDialog waitDialog = new WaitDialog(null, "Update...");      
        
        InputStream inputStream = new BufferedInputStream(new FileInputStream(fileName));
        
        Result result = ansicht.calculateResult(inputStream);  // Java3D calculation
        
        waitDialog.close();

        return result;
    }

In this version, the waitDialog is not shown but calculteResult works fine. Therefore, as indicate above, I copied the working logic from another place, which looks like this...

public Result doIt(String fileName) throws Exception {
        
        WaitDialog waitDialog = new WaitDialog(null, "Update...");          
        
        SwingWorker<Result,Void> worker = new SwingWorker<Result,Void>() {                          
            @Override
            protected Result doInBackground() throws Exception {
                InputStream inputStream = new BufferedInputStream(new FileInputStream(fileName));
        
                Result result = ansicht.calculateResult(inputStream);  // Java3D calculation
                return result;                                                                  
            }

            @Override
            protected void done() {
                waitDialog.close();                                                             
            }
        };
        worker.execute();                                                                       
        
        Result result = worker.get();
        return result;
    }

which shows no effect. No message shown and calculateResult works still fine. So I looked in the web what alternatives I might have and tried this...

public Result doIt(String fileName) throws Exception {
        
        WaitDialog waitDialog = new WaitDialog(null, "Update...");  
        
        InputStream inputStream = new BufferedInputStream(new FileInputStream(fileName));

        SwingUtilities.invokeAndWait(() -> {
            try {
                result = ansicht.calculateResult(inputStream);  // Java3D calculation
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        
        waitDialog.close();

        return result;
    }

result being now a Class variable. This version shows the wait dialog, but the calculateResult call is not coming back and the dialog doesn't disappear.

I also tried a version with an old fashioned Thread, which I found in the web...

public Result doIt(String fileName) throws Exception {

        WaitDialog waitDialog = new WaitDialog(null, "Update...");  

        Thread thread = new Thread(() -> {                                  
                SwingUtilities.invokeAndWait(() -> {
                    try {
                        InputStream inputStream = new BufferedInputStream(new FileInputStream(fileName));
                        result = ansicht.calculateResult(inputStream);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        thread.start(); 

        while(thread.isAlive()) {};
        waitDialog.close();

        return result;
    }

also not doing what I want.

Can you please advise me how to do this? Thank you in advance.


Solution

  • The way swing works is you have the Event Dispatch Thread (EDT) that is constantly running and processing GUI events. When you have a long running task, you run it on a different thread, then tell the EDT that your are finished.

    Is "doIt" being called on the EDT? eg did you start it from an event like a button? I am going to assume it is.

    All of your versions are blocking "doIt" until after the result is finished. You need to let doIt finish. I would suggest a call back.

    public void processResult( Result result){
      //this should be a continuation of the code from where you call doIt
    }
    

    Then in second version you can update it.

    public void doIt(String fileName) throws Exception {
        
        WaitDialog waitDialog = new WaitDialog(null, "Update...");          
        
        SwingWorker<Result,Void> worker = new SwingWorker<Result,Void>() {                          
            @Override
            protected Result doInBackground() throws Exception {
                InputStream inputStream = new BufferedInputStream(new   FileInputStream(fileName));
        
                Result result = ansicht.calculateResult(inputStream);  // Java3D calculation
                return result;                                                                  
            }
    
            @Override
            protected void done() {
                waitDialog.close();
                processResult( get() );                                                             
            }
        };
        worker.execute();                                                                       
        
        //this is blocking and preventing your gui from updating.
        //Result result = worker.get();
        //return result;
    }
    

    This is the worst version of your code.

    SwingUtilities.invokeAndWait(() -> {
            try {
                result = ansicht.calculateResult(inputStream);  // Java3D calculation
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    

    That you are literally posting your long running task to the EDT and blocking the current thread, which I suspect is also the EDT.