14

The problem is this:
I've a swing application running, at a certain point a dialog requires to insert username and password and to press "ok".
I would like that when the user press "ok" the swing application does in this order:

  1. Open a "Please wait" JDialog
  2. Make some operation(eventually displaying some other JDialog or JOptionPane)
  3. When it finishes with the operation close the "please wait" JDialog

This is the code that I wrote in the okButtonActionPerformed():

private void okButtonActionPerformed(java.awt.event.ActionEvent evt) { 
    //This class simply extends a JDialog and contains an image and a jlabel (Please wait)
    final WaitDialog waitDialog = new WaitDialog(new javax.swing.JFrame(), false);    
    waitDialog.setVisible(true);
    ... //Do some operation (eventually show other JDialogs or JOptionPanes)
    waitDialog.dispose()
}

This code obviously doesn't works because when I call the waitDialog in the same thread it blocks all till I don't close it.
So I tried to run it in a different thread:

private void okButtonActionPerformed(java.awt.event.ActionEvent evt) { 
    //This class simply extends a JDialog and contains an image and a jlabel (Please wait)
    final WaitDialog waitDialog = new WaitDialog(new javax.swing.JFrame(), false);    
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            waitDialog.setVisible(true);
        }
    });
    ... //Do some operation (eventually show other JDialogs or JOptionPanes)
    waitDialog.dispose()
}

But also this doesn't work because the waitDialog is not displayed immediately but only after that the operation completed their work (when they show a joption pane "You are logged in as...")

I also tried to use invokeAndWait instead of invokeLater but in this case it throws an exception:

Exception in thread "AWT-EventQueue-0" java.lang.Error: Cannot call invokeAndWait from the event dispatcher thread

How can I do?

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
user2572526
  • 1,219
  • 2
  • 17
  • 35

3 Answers3

34

Consider using a SwingWorker to do your background work, and then closing the dialog either in the SwingWorker's done() method or (my preference) in a PropertyChangeListener that is added to the SwingWorker.

e.g.,

import java.awt.BorderLayout;
import java.awt.Dialog.ModalityType;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;    
import javax.swing.*;

public class PleaseWaitEg {
   public static void main(String[] args) {
      JButton showWaitBtn = new JButton(new ShowWaitAction("Show Wait Dialog"));
      JPanel panel = new JPanel();
      panel.add(showWaitBtn);
      JFrame frame = new JFrame("Frame");
      frame.getContentPane().add(panel);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);

   }
}

class ShowWaitAction extends AbstractAction {
   protected static final long SLEEP_TIME = 3 * 1000;

   public ShowWaitAction(String name) {
      super(name);
   }

   @Override
   public void actionPerformed(ActionEvent evt) {
      SwingWorker<Void, Void> mySwingWorker = new SwingWorker<Void, Void>(){
         @Override
         protected Void doInBackground() throws Exception {

            // mimic some long-running process here...
            Thread.sleep(SLEEP_TIME);
            return null;
         }
      };

      Window win = SwingUtilities.getWindowAncestor((AbstractButton)evt.getSource());
      final JDialog dialog = new JDialog(win, "Dialog", ModalityType.APPLICATION_MODAL);

      mySwingWorker.addPropertyChangeListener(new PropertyChangeListener() {

         @Override
         public void propertyChange(PropertyChangeEvent evt) {
            if (evt.getPropertyName().equals("state")) {
               if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
                  dialog.dispose();
               }
            }
         }
      });
      mySwingWorker.execute();

      JProgressBar progressBar = new JProgressBar();
      progressBar.setIndeterminate(true);
      JPanel panel = new JPanel(new BorderLayout());
      panel.add(progressBar, BorderLayout.CENTER);
      panel.add(new JLabel("Please wait......."), BorderLayout.PAGE_START);
      dialog.add(panel);
      dialog.pack();
      dialog.setLocationRelativeTo(win);
      dialog.setVisible(true);
   }
}

Notes:

  • A key concept is to set everything up, add the PropertyChangeListener, get the SwingWorker running, all before displaying the modal dialog, because once the modal dialog is shown, all code flow from the calling code is frozen (as you've found out).
  • Why do I prefer the PropertyChangeListener to using the done method (as Elias demonstrates in his decent answer here, which I've up-voted) -- using the listener provides more separation of concerns, looser coupling. This way the SwingWorker has to know nothing of the GUI code that is using it.
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • It seems to work, thanks (thanks also to @Elias) :) . But now there's another small problem. When I close the main application (with dispose() ) the SwingWorker thread continue to run. Have I to close it explicitly? Doesn't it terminate when reach the doInBackground's return statement? – user2572526 Nov 28 '13 at 17:08
  • @user2572526: yes it will close automatically when it finishes. How do you know it is still running? – Hovercraft Full Of Eels Nov 28 '13 at 17:49
  • I solved, it was not a problem of SwingWorker, it was a mistake with the JDialog initialization. (I kenw that it was still running by the Netbeans profiler). Thanks a lot for your help. – user2572526 Nov 29 '13 at 08:52
  • 1
    What if `execute` ends before `dialog.pack()`? Maybe app may stuck then? – Vit Bernatik May 18 '15 at 22:54
  • @VitBernatik: I don't know, but it should be tested. Let me see what I can do on this... – Hovercraft Full Of Eels May 18 '15 at 22:56
  • 1
    @Hovercraft... I did test it and it seems that `dispose()` waits until `setVisible(true)` is called therefore it is ok. – Vit Bernatik May 22 '15 at 12:38
  • 1
    @VitBernatik can you clarify whether `dispose()` is _guaranteed_ to wait until `setVisible(true)` is called, and if so, where this is documented? I can't find anything in https://docs.oracle.com/javase/7/docs/api/java/awt/Window.html#dispose() – beldaz Jul 16 '16 at 10:20
  • 1
    @beldaz: I just test on my software. With some long delays. I can not guarantee it will work in newer versions. – Vit Bernatik Jul 18 '16 at 15:46
  • @VitBernatik Thanks. I see there's a similar example given in https://docs.oracle.com/javase/7/docs/api/javax/swing/SwingWorker.html#get() using a `PropertyChangeListener ` – beldaz Jul 18 '16 at 19:52
11
public void okButtonActionPerformed(ActionEvent e) {

    final JDialog loading = new JDialog(parentComponent);
    JPanel p1 = new JPanel(new BorderLayout());
    p1.add(new JLabel("Please wait..."), BorderLayout.CENTER);
    loading.setUndecorated(true);
    loading.getContentPane().add(p1);
    loading.pack();
    loading.setLocationRelativeTo(parentComponent);
    loading.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
    loading.setModal(true);

    SwingWorker<String, Void> worker = new SwingWorker<String, Void>() {
        @Override
        protected String doInBackground() throws InterruptedException 
            /** Execute some operation */   
        }
        @Override
        protected void done() {
            loading.dispose();
        }
    };
    worker.execute();
    loading.setVisible(true);
    try {
        worker.get();
    } catch (Exception e1) {
        e1.printStackTrace();
    }
}
elias
  • 15,010
  • 4
  • 40
  • 65
2

A variation of the above answer

It's an easy and replicable way to do...

//This code goes inside your button action   
DialogWait wait = new DialogWait();

SwingWorker<Void, Void> mySwingWorker = new SwingWorker<Void, Void>() {

    @Override
    protected Void doInBackground() throws Exception {

        //Here you put your long-running process...

        wait.close();
        return null;
    }
};

mySwingWorker.execute();
wait.makeWait("Test", evt);
//end


//Create this class on your project
class DialogWait {

private JDialog dialog;

public void makeWait(String msg, ActionEvent evt) {

    Window win = SwingUtilities.getWindowAncestor((AbstractButton) evt.getSource());
    dialog = new JDialog(win, msg, Dialog.ModalityType.APPLICATION_MODAL);

    JProgressBar progressBar = new JProgressBar();
    progressBar.setIndeterminate(true);
    JPanel panel = new JPanel(new BorderLayout());
    panel.add(progressBar, BorderLayout.CENTER);
    panel.add(new JLabel("Please wait......."), BorderLayout.PAGE_START);
    dialog.add(panel);
    dialog.pack();
    dialog.setLocationRelativeTo(win);
       dialog.setVisible(true);
   }

   public void close() {
       dialog.dispose();
   }
}