Почему InvokeLater заставляет мой JFrame отображаться неправильно? - PullRequest
5 голосов
/ 04 декабря 2009

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

У меня довольно большой проект, который обрабатывает заказы на работы для ремонтного бизнеса. Это все связано с базой данных, много-много страниц кода и классов. Но я только добавил небольшой фрагмент кода во внешний интерфейс, который по существу проверяет наличие новых сообщений в нашей области заметок.

В любом случае, я отображаю простой JFrame с двумя JLabel s, в то время как отдельный поток запрашивает базу данных. Все это происходит в начале программы. Проблема в том, что мое маленькое «пожалуйста, подождите» JFrame предлагает свой фрейм, но без кишок, без фона и без JLabel s во время ожидания (что является остальной частью программы загрузка, а не поток базы данных), послесловия, которые он отображает, но к тому времени он теряет свою точку.

Я написал следующий пример программы. Он отображает простой JFrame (CheckingMessagesGUI: JFrame с двумя JLabel s, больше ничего) в течение 5 секунд, а затем отображает пример (основная программа) JFrame , затем мгновенно закрывается (System.exit(0)) в этом примере, конечно, моя настоящая программа продолжает делать намного больше. Я обнаружил, что invokeLater, кажется, вызывает проблему. Как только таймер отключения закончится, появится окно, но код для его отображения был задан перед командой Thread.sleep, и должен был быть выполнен в правильном порядке?

Мой вопрос: почему invokeLater заставляет мой JFrame отображаться неправильно?

Насколько я понимаю, цель invokeLater состоит в том, чтобы элементы работали в правильном потоке событий AWT, что заставило бы меня думать, что это окно будет окрашено правильно. В любом случае, я уверен, что упускаю что-то очевидное. Я закомментировал часть invokeLater в приведенном ниже коде, и она работает правильно, если вы положите ее обратно, она не ...

Большое спасибо заранее.

package javaapplication6;

public class Example extends javax.swing.JFrame {          
    public Example() {
        System.out.println("Example started");
        setBounds(100,100,200,200);

        System.out.println("cmGUI instantiated");
        CheckingMessagesGUI cmGUI = new CheckingMessagesGUI();
        System.out.println("Set cmGUI visible");
        cmGUI.setVisible(true);
        cmGUI.validate();
        try {
            System.out.println("timer started");
            Thread.sleep(5000);
            System.out.println("timer done");
        } catch(InterruptedException e){
        }
        System.exit(0);
    }

    public static void main(String[] args) {
        /*java.awt.EventQueue.invokeLater(new Runnable() {
        @Override
        public void run() { */
        System.out.println("Started");
        System.out.println("example Instantiated");
        Example example = new Example();
        System.out.println("example visible");
        example.setVisible(true);
        /*      }
        });
        */
    }
}

UPDATE: Чтобы уточнить, я понимаю, что Thread.sleep() заблокирует все, но разве мой CheckingMessagesGUI не должен быть полностью прорисован до того, как я вызову sleep? Это проблема.

Ответы [ 5 ]

4 голосов
/ 04 декабря 2009

invokeLater запускает Runnable в потоке диспетчеризации событий, который также используется для обновления графического интерфейса.
Ваш сон блокирует этот поток, поэтому графический интерфейс также не получает обслуживания , обновления не могут быть выполнены, пока вы не вернетесь из кода invokeLater.
Вот почему вы не должны делать никаких длинных (трудоемких) вычислений в этой теме. Они должны быть сделаны в другой (новой) теме.

Очередь отправки событий состояния

Задачи в потоке диспетчеризации событий должны быстро завершаться; если они этого не делают, резервные копии необработанных событий и пользовательский интерфейс перестают отвечать на запросы.

Ваш код может быть изменен на (не проверен):

public Example(){
    System.out.println("Example started");
    setBounds(100,100,200,200);

    System.out.println("cmGUI instantiated");
    CheckingMessagesGUI cmGUI = new CheckingMessagesGUI();
    System.out.println("Set cmGUI visible");
    cmGUI.setVisible(true);
    cmGUI.validate();

    Thread thread = new Thread(new Runnable() {
        try {
            System.out.println("timer started");
            Thread.sleep(5000);
            System.out.println("timer done");
        } catch(InterruptedException e) {
        }
        System.exit(0);
    });
    thread.start();
}

EDIT: давайте пойдем немного глубже (и это мой взгляд на работу Swing / AWT).
Я полагаю, что «пожалуйста, подождите» (см. Комментарии) должен отображаться в классе CheckingMessagesGUI, но это не так.
Это связано с тем, как работает графический интерфейс. Он ничего не меняет напрямую на дисплее, если вы вызываете соответствующие (Swing) методы (draw, setText, setLocation, ...); он просто ставит в очередь событие в очереди событий. Поток отправки событий является (должен быть) единственным потоком, который читает эту очередь и обрабатывает события. Пока он блокируется - в данном случае в режиме ожидания - изменения в графическом интерфейсе не будут отображаться. GUI заморожен.

EDIT2:
invokeLater Runnable - это , добавленный к концу очереди, которая должна быть позднее выполнена EDT после обработки всех ожидающих событий, следующая команда после вызова invokeLater будет выполнена.
invokeAndWait то же, что и выше, но фактические блоки Thread до тех пор, пока EDT не выполнит Runnable (после ожидающих событий), то есть команда, следующая за invokeAndWait, будет запущена только после того, как был выполнен переданный Runnable.

1 голос
/ 04 декабря 2009

мое понимание того, что цель InvokeLater для запуска элементов в правильном потоке событий AWT

Это правильно.

Однако это также означает, что Thread.sleep () выполняется в EDT, что означает, что графический интерфейс не может перерисовываться сам, поскольку вы только что сказали EDT перевести в спящий режим. Вам нужно использовать отдельный поток для вашей длительной задачи.

Прочтите раздел из учебника по Swing на Параллелизм для получения дополнительной информации о EDT.

но я хотел сказать, что мое "пожалуйста, подождите" (CheckingMessagesGUI) должен иметь уже был нарисован полностью, прежде чем я называется сон. Разве это не должно быть правдой?

Вот мое упрощенное понимание процесса. Фрейм создается и отображается, потому что это собственный компонент ОС. Однако contentPane и дочерний компонент являются легкими компонентами, что означает, что диспетчер перерисовки Swing планирует, когда они должны быть перекрашены. Таким образом, перед назначением перекраски EDT переводится в спящий режим, и перекраска не может быть выполнена до тех пор, пока сон не закончится.

Более подробную информацию о Repaint Manager можно найти в статье о Paintng в AWT и Swing .

0 голосов
/ 26 января 2010

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

public void method(){
    final PleaseWaitWindow window = new PleaseWaitWindow();

    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            //stuff that you want to do that is preventing window to display

            window.dispose();
        }
    }
    thread.start();
}
0 голосов
/ 15 декабря 2009

Невидимые компоненты не окрашены.

0 голосов
/ 04 декабря 2009

Мой ответ состоял в том, что когда GUI создается, он не окрашивается автоматически, вместо этого в очередь EDT помещается вызов рисования. Если в том же методе вы создаете объект GUI и setVisible (true), то в следующих нескольких строках выполняется что-то интенсивное, это блокирует последующий вызов рисования, потому что он не будет помещен в очередь EDT, пока этот метод с интенсивным материалом) заканчивает. Также, как было упомянуто, рамка или граница находятся на стороне платформы уравнения (следовательно, рисуется), а остальное (Jlabel, контейнер, фон и т. Д.) Находится на стороне Java и не происходит, пока краска на самом деле не запускается ( т.е. очередь EDT доходит до него). Мой пример кода работал без вызова InvokeLater, потому что он выполнял интенсивную работу в потоке init и позволял потоку EDT все еще рисовать.

...