Swing: Как запустить задание из потока AWT, но после того, как окно было выложено? - PullRequest
1 голос
/ 27 декабря 2010

Мой полный графический интерфейс работает внутри потока AWT, потому что я запускаю главное окно, используя SwingUtilities.invokeAndWait(...).

Теперь у меня есть JDialog, который должен просто отобразить JLabel, который указывает, что определенное задание выполняется, и закрыть это диалоговое окно после того, как задание было завершено.

Проблема в том, что метка не отображается. Эта работа, кажется, была начата до того, как JDialog был полностью выложен.

Когда я просто позволяю диалоговому окну открываться, не дожидаясь задания и не закрывая, отображается метка .

Последнее, что диалог делает в своем ctor, это setVisible(true).
Такие вещи, как revalidate(), repaint(), ... тоже не помогают.

Даже когда я запускаю поток для отслеживаемого задания и жду его, используя someThread.join(), это не помогает, потому что текущий поток (который является потоком AWT) заблокирован join, я полагаю.

Замена JDialog на JFrame тоже не помогает.

Итак, концепция вообще неверна? Или я могу сделать это для выполнения определенной работы после , когда JDialog (или JFrame) полностью выложен?

Упрощенный алгоритм того, чего я пытаюсь достичь:

  • Создать подкласс JDialog
  • Убедитесь, что он и его содержимое полностью выложены
  • Запустите процесс и дождитесь его завершения (с резьбой или нет, не имеет значения)
  • Закрыть диалоговое окно

Мне удалось написать воспроизводимый контрольный пример:

РЕДАКТИРОВАТЬ Задача из ответа теперь решается: В этом сценарии использования отображается метка, но она не закрывается после "смоделированного процесса" из-за модальности диалога.

import java.awt.*;
import javax.swing.*;

public class _DialogTest2 {
    public static void main(String[] args) throws Exception {
        SwingUtilities.invokeAndWait(new Runnable() {
            final JLabel jLabel = new JLabel("Please wait...");
            @Override
            public void run() {
                JFrame myFrame = new JFrame("Main frame");
                myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                myFrame.setSize(750, 500);
                myFrame.setLocationRelativeTo(null);
                myFrame.setVisible(true);

                JDialog d = new JDialog(myFrame, "I'm waiting");
                d.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);

                d.add(jLabel);
                d.setSize(300, 200);
                d.setLocationRelativeTo(null);
                d.setVisible(true);

                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(3000); // simulate process
                            jLabel.setText("Done");
                        } catch (InterruptedException ex) {
                        }
                    }
                });

                d.setVisible(false);
                d.dispose();

                myFrame.setVisible(false);
                myFrame.dispose();
            }
        });
    }
}

Ответы [ 5 ]

4 голосов
/ 27 декабря 2010

Попробуйте это:

package javaapplication3;

import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class Main {

public static void main(String[] args)
        throws Exception {
    SwingUtilities.invokeAndWait(new Runnable() {

        final JLabel jLabel = new JLabel("Please wait...");

        @Override
        public void run() {
            JFrame myFrame = new JFrame("Main frame");
            myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            myFrame.setSize(750, 500);
            myFrame.setLocationRelativeTo(null);
            myFrame.setVisible(true);

            JDialog d = new JDialog(myFrame, "I'm waiting");

            d.add(jLabel);
            d.setSize(300, 200);
            d.setLocationRelativeTo(null);
            d.setVisible(true);

            new Thread(new Runnable() {

                @Override
                public void run() {

                public void run() {
                    try {
                        Thread.sleep(3000); // simulate process
                        jLabel.setText("Done");   // HERE: should be done on EDT!
                    } catch (InterruptedException ex) {
                    }
                }
            }).start();


        }
    });
}
}

Это работает, но это не правильно. Я объясню, что происходит.

Ваш метод main() начинается в основной теме. Весь код, связанный с Swing, должен выполняться в потоке EDT. И именно поэтому Вы используете (правильно) SwingUtilities.invokeAndWait(...). Пока все хорошо.

Но на EDT не должно быть долго выполняющихся задач. Поскольку Swing является однопоточным, любые длительные процессы блокируют EDT. Поэтому ваш код Thread.wait(...) никогда не должен выполняться на EDT. И это моя модификация. Я завернул вызов в другой теме. Так что это идиоматическая долгосрочная обработка задач для Swing. Я использовал класс Thread для краткости, но я бы порекомендовал перейти с SwingWorker thread.

И очень важно: я делаю одну ошибку в предыдущем примере. Смотрите строку с комментарием "ЗДЕСЬ"? Это еще одно нарушение правил Swing для одного потока. Код внутри потока выполняется снаружи EDT, поэтому он никогда не должен касаться Swing. Так что этот код неверен с правилом Swing для одного потока. Это не безопасно от зависания GUI.

Как это исправить? Просто. Вы должны поместить ваш вызов в другой поток и поместить его в очередь EDT. Поэтому правильный код должен выглядеть так:

    SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                jLabel.setText("Done");
            }
        });

РЕДАКТИРОВАТЬ : Этот вопрос очень сильно затрагивает вопросы, связанные с Swing. Не могу объяснить их все сразу ... Но вот еще один фрагмент, который делает то, что вы хотите:

public static void main(String[] args)
        throws Exception {
    SwingUtilities.invokeAndWait(new Runnable() {

        final JFrame myFrame = new JFrame("Main frame");
        final JLabel jLabel = new JLabel("Please wait...");
        final JDialog d = new JDialog(myFrame, "I'm waiting");

        @Override
        public void run() {
            myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            myFrame.setSize(750, 500);
            myFrame.setLocationRelativeTo(null);
            myFrame.setVisible(true);

            d.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);

            d.add(jLabel);
            d.setSize(300, 200);
            d.setLocationRelativeTo(null);
            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        Thread.sleep(3000); // simulate process
                        System.out.println("After");
                        SwingUtilities.invokeLater(new Runnable() {

                            public void run() {


                                d.setVisible(false);
                                d.dispose();

                                myFrame.setVisible(false);
                                myFrame.dispose();
                            }
                        });
                    } catch (InterruptedException ex) {
                    }
                }
            }).start();
            d.setVisible(true);

        }
    });
}

Подводя итог:

  • Весь код, относящийся к Swing, должен работать на EDT
  • Весь долго работающий код не должен работать на EDT
  • Код можно запустить в EDT, используя SwingUtilities. ...
    • invokeAndWait() - как видно из названия, вызов синхронный,
    • invokeLater() - вызвать код «когда-нибудь», но немедленно вернуться
  • Если вы используете EDT и хотите вызвать код в другом потоке, вы можете:
    • Создайте новый Thread (передайте Runnable в новый поток или переопределите его start() метод) и запустите его,
    • Создайте новую ветку SwingWorker с некоторыми дополнениями.
    • Возможно, использовать любой другой механизм потоков (например, потоки Executor).

Типичный сценарий GUI включает в себя:

  1. Создание компонентов графического интерфейса,
  2. Подключение слушателей изменения свойств,
  3. Выполнение кода, связанного с действиями пользователя (т.е. запуск прослушивателей изменения свойств),
  4. Выполнение, возможно, трудоемких задач,
  5. Обновление состояния графического интерфейса,

1., 2., 3. и 4. запустить на EDT . 4. не должен. Существует много способов написания правильного многопоточного кода. Наиболее громоздким является использование класса Thread, который поставляется с ранними версиями Java. Если делать это наивно, ресурсы могут быть потрачены впустую (слишком много запущенных потоков одновременно). Также обновление графического интерфейса является громоздким. Использование SwingWorker немного облегчает проблему. Он гарантированно ведет себя правильно при запуске, запуске и обновлении графического интерфейса (у каждого есть специальный метод, который вы можете переопределить, и убедитесь, что он работает в правильном потоке).

3 голосов
/ 27 декабря 2010

Как указано в предыдущих ответах, вы ДОЛЖНЫ запустить длинное задание в потоке, ОТЛИЧНО от EDT.

Самое простое ИМХО - использовать SwingWorker и добавить прослушиватель для удаления диалогового окна после завершения.Вот пример:

SwingWorkerCompletionWaiter.java

public class SwingWorkerCompletionWaiter implements PropertyChangeListener {
  private JDialog dialog;

  public SwingWorkerCompletionWaiter(JDialog dialog) {
      this.dialog = dialog;
  }

  @Override
  public void propertyChange(PropertyChangeEvent event) {
      if ("state".equals(event.getPropertyName())
              && SwingWorker.StateValue.DONE == event.getNewValue()) {
          dialog.setVisible(false);
          dialog.dispose();
      }
  }
}

И следующий код выполнит длинную задачу:

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

  @Override
  protected Void doInBackground() throws Exception {
    // do something long
  }
};
JDialog dialog = new JDialog();
    // initialize the dialog here
worker.addPropertyChangeListener(new SwingWorkerCompletionWaiter(dialog));
worker.execute();
dialog.setVisible(true);

И если вы хотите создать какую-тоВ диалоговом окне «Пожалуйста, подождите» может быть проще просто расширить JDialog и использовать расширенный класс везде, где вам нужно.

0 голосов
/ 27 декабря 2010

Вы должны использовать invokeLater вместо InvokeAndWait.

public class DialogTest {
    public static void main(String[] args) throws Exception {
        final JLabel comp =  new JLabel("the job has started");;
        final JFrame myFrame = new JFrame("Main frame");
        final JDialog d = new JDialog(myFrame, "I'm waiting");
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {

                myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                myFrame.setSize(750, 500);
                myFrame.setLocationRelativeTo(null);
                myFrame.setVisible(true);

                 d.setModalityType(JDialog.ModalityType.APPLICATION_MODAL); 
                d.add(comp);
                d.setSize(300, 200);
                d.setLocationRelativeTo(null);
                d.setVisible(true);


            }
        });
        try {
            Thread.sleep(3000);
            // d.setVisible(false);     
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    comp.setText("the job has finished");
                     d.setVisible(false);         
                     d.dispose();         
                     myFrame.setVisible(false);         
                     myFrame.dispose(); 
                }
            });

        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}
0 голосов
/ 27 декабря 2010

Вам нужно запустить задание в фоновом потоке или в основном потоке приложения. JDialog и JFrame должны быть созданы в потоке диспетчеризации событий (EDT) с SwingUtilities.invokeLater();. Затем дождитесь окончания задания и закройте диалоговое окно в (EDT). Поскольку макет и задание находятся в двух отдельных потоках, проблем не должно быть. На самом деле работает следующая модификация вашего примера:

public static void main(String[] args) throws Exception {
    final JFrame myFrame = new JFrame("Main frame");
    final JDialog d = new JDialog(myFrame, "I'm waiting");

    final Thread backgroundJob = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000); // simulate process
                }
                catch (InterruptedException ex) {
                    Logger.getLogger(NewClass.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
    });
    backgroundJob.start();

    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            myFrame.setSize(750, 500);
            myFrame.setLocationRelativeTo(null);
            myFrame.setVisible(true);

            d.add(new JLabel("Please wait..."));
            d.setSize(300, 200);
            d.setLocationRelativeTo(null);
            d.setVisible(true);
        }
    });

    backgroundJob.join();

    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            d.setVisible(false);
        }
    });
}
0 голосов
/ 27 декабря 2010

Во-первых, извините за размышления о работе, я не совсем понял, что вы пытались сделать, прежде чем опубликовать свой код.

Тем не менее, я не очень знаком с этим способом созданиядиалоговые окна, окна и общие сведения, поэтому я предлагаю вам создать отдельный класс для вашего JDialog.Просто простой класс, что-то вроде этого:

public class MyDialog extends JDialog
{
    public MyDialog(JFrame owner, String title)
    {
        super(owner, title);

        d.setSize(300, 200);           
        d.setLocationRelativeTo(null);           
        d.setVisible(true);
        d.setDefaultCloseOperation(DISPOSE_ON_CLOSE );

        Container c = getContentPane();
        c.setLayout(new FlowLayout());

        c.add(new JLabel("Please wait..."));  
    }
}

И запустите диалог следующим образом:

new MyDialog(this, "Your title");

Это может выглядеть как дерьмо, но это может работать (я не проверял).

Надеюсь, это поможет.

...