Home > Software engineering >  Java Swing Wait message will not show up
Java Swing Wait message will not show up

Time:06-06

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.

CodePudding user response:

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.

CodePudding user response:

In addition to (and hopefully supporting) matt's answer.

Swing is not thread and is single threaded, this means that you should try and update the state of the UI (or anything the UI relies on) from outside the context of the Event Dispatching Thread or execute long running or blocking operations from within the context of the Event Dispatching Thread.

See Concurrency in Swing for more details.

The important point here is the SwingWorker#get method. The JavaDocs state:

Waits if necessary for the computation to complete, and then retrieves its result.

Note: calling get on the Event Dispatch Thread blocks all events, including repaints, from being processed until this SwingWorker is complete.

When you want the SwingWorker to block on the Event Dispatch Thread we recommend that you use a modal dialog.

This would go a long way to explaining your issue.

The following is another possible approach. It makes use of dedicated SwingWorker...

public class MagicWorker extends SwingWorker<String, Void> {
    @Override
    protected String doInBackground() throws Exception {
        // This is where the  magic happens
        Thread.sleep(5000);
        return "Rabbit";
    }
}

and is monitored through the use of PropertyChangeListener...

worker.addPropertyChangeListener(new PropertyChangeListener() {
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (worker.getState() == SwingWorker.StateValue.DONE) {
            dialog.dispose();
            try {
                String magic = worker.get();
                observer.magicDidHappen(magic);
            } catch (InterruptedException | ExecutionException ex) {
                observer.magicDidFailToHappen(ex);
            }
        }
    }
});

and makes use of an observer (AKA listener) to notify the caller that the operation completed or failed, so that the interested party can deal with the result of the operation.

It's important to note that this is just one way you might achieve this and matt's solution is also a valid solution!

Runnable example...

import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.border.EmptyBorder;

public class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new MainPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class MainPane extends JPanel {

        public interface MagicObserver {
            public void magicDidHappen(String result);
            public void magicDidFailToHappen(Exception exp);
        }

        public MainPane() {
            setBorder(new EmptyBorder(32, 32, 32, 32));
            setLayout(new GridBagLayout());

            JButton makeMagic = new JButton("Make the magic happen");
            add(makeMagic);

            makeMagic.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    makeMagic.setEnabled(false);
                    makeMagic(new MagicObserver() {
                        @Override
                        public void magicDidHappen(String result) {
                            makeMagic.setEnabled(true);
                            JOptionPane.showMessageDialog(MainPane.this, result, "Magic", JOptionPane.INFORMATION_MESSAGE);
                        }

                        @Override
                        public void magicDidFailToHappen(Exception exp) {
                            makeMagic.setEnabled(true);
                            exp.printStackTrace();
                            JOptionPane.showMessageDialog(MainPane.this, "Failed to make magic", "Error", JOptionPane.ERROR_MESSAGE);
                        }
                    });
                }
            });
        }

        protected void makeMagic(MagicObserver observer) {
            MagicWorker worker = new MagicWorker();
            JDialog dialog = new JDialog(SwingUtilities.windowForComponent(this));
            dialog.add(new WaitPane());
            dialog.setModal(true);
            dialog.setUndecorated(true);
            dialog.pack();
            dialog.setLocationRelativeTo(this);

            worker.addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if (worker.getState() == SwingWorker.StateValue.DONE) {
                        dialog.dispose();
                        try {
                            String magic = worker.get();
                            observer.magicDidHappen(magic);
                        } catch (InterruptedException | ExecutionException ex) {
                            observer.magicDidFailToHappen(ex);
                        }
                    }
                }
            });
            worker.execute();
            dialog.setVisible(true);
        }

    }

    public class WaitPane extends JPanel {
        public WaitPane() {
            setBorder(new EmptyBorder(32, 32, 32, 32));
            setLayout(new GridBagLayout());

            JProgressBar progressBar = new JProgressBar();
            progressBar.setIndeterminate(true);

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.insets = new Insets(8, 8, 8, 8);
            gbc.gridwidth = GridBagConstraints.REMAINDER;

            add(new JLabel("Making the magic happen"), gbc);
            add(progressBar, gbc);
        }
    }

    public class MagicWorker extends SwingWorker<String, Void> {
        @Override
        protected String doInBackground() throws Exception {
            // This is where the  magic happens
            Thread.sleep(5000);
            return "Rabbit";
        }
    }
}
  • Related