Сделайте качающуюся нить, которая показывает "Подождите" JDialog

Проблема заключается в следующем:
У меня запущено приложение swing, в какой-то момент диалог требует вставить имя пользователя и пароль и нажать "ok".
Я хотел бы, чтобы пользователь, нажав "ok", применил приложение swing в следующем порядке:

  • Откройте "Подождите" JDialog
  • Сделайте некоторую операцию (в конечном итоге отобразите некоторые другие JDialog или JOptionPane)
  • Когда он закончит с закрытием операции, "пожалуйста, подождите" JDialog

Это код, который я написал в 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()
}

Этот код явно не работает, потому что, когда я вызываю waitDialog в том же потоке, он блокирует все, пока я его не закрываю.
Поэтому я попытался запустить его в другом потоке:

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()
}

Но также это не работает, потому что waitDialog не отображается сразу, но только после этого операция завершила свою работу (когда они показывают панель joption "Вы вошли в систему как..." )

Я также попытался использовать invokeAndWait вместо invokeLater, но в этом случае он выдает исключение:

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

Как я могу это сделать?

Ответ 1

Подумайте о том, как использовать SwingWorker для выполнения фоновой работы, а затем закрыть диалоговое окно в методе SwingWorker done() или (в моих предпочтениях) в PropertyChangeListener, который добавлен в SwingWorker.

например.

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);
   }
}

Примечания:

  • Ключевой концепцией является установка всего, добавление PropertyChangeListener, запуск SwingWorker, все до отображение модального диалога, так как после отображения модального диалога весь поток кода от вызывающего кода (как вы узнали).
  • Почему я предпочитаю PropertyChangeListener использовать готовый метод (поскольку Elias демонстрирует в своем достойном ответе здесь, который я проголосовал) - использование слушателя обеспечивает большее разделение проблем, ослабление связи. Таким образом, SwingWorker не должен знать ничего о коде GUI, который его использует.

Ответ 2

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();
    }
}

Ответ 3

Вариант приведенного выше ответа

Это простой и воспроизводимый способ сделать...

//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();
   }
}