Долгий процесс под Swing GUI: непредвиденная задержка

Чтобы объяснить мой вопрос, вот MCVE, где нажатие JButton на JDialog A открывает JDialog B:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JDialog;

public class  DiagA extends JDialog  {

    private DiagB diag;

    public  DiagA() {

        super();
        setTitle("main diag");
        setSize(200, 150);
        setLocation(400,400);

        JButton btn = new JButton("Show DiagB");
        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {

                showDiag();
            }
        });
        add(btn, BorderLayout.NORTH);

        //make main frame visible
        setVisible(true);
    }

    void showDiag() {

        if(diag == null) {

            diag = new DiagB();

            //this prints out as expected
            System.out.println("set visible done");

            try {
                Thread.sleep(3000);
            } catch (InterruptedException ex) {}

            //only after the delay diag shows in full
        }
    }

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

class DiagB extends JDialog  {

    public  DiagB() {

        super();
        setTitle("2nd diag");
        setSize(150, 100);
        setLocation(600,420);
        setLayout(new FlowLayout(FlowLayout.CENTER));
        getContentPane().setBackground(Color.YELLOW);
        setVisible(true);
    }
}

Как вы можете видеть в коде, я добавил 3-секундную задержку после создания DiagB. Нажатие кнопки DiagBпоказывает следующее:

введите здесь описание изображения

Только после окончания 3-секундной задержки DiagBотображается полностью:

введите здесь описание изображения

Мои вопросы:
a. Почему DiagBне отображается полностью после того, как он построен? (Показывается полностью только при возврате showDiag()).
б. Причина моего вопроса в том, что DiagB нужно обновить долгими процессами в DiagA.
Как правильно обновлять? Требуется ли использование SwingWorker для каждого процесса обновления?


person c0der    schedule 14.09.2016    source источник
comment
Если это для отображения, а не для взаимодействия с пользователем, это не должно быть JDialog, которое, ну, для диалога с пользователем. И в обработчике событий никогда не должно быть приостановки (или любой другой длительной операции).   -  person RealSkeptic    schedule 14.09.2016
comment
@RealSkeptic Я понимаю, что вы говорите о длинных операциях в обработчике событий (JDialog или JFrame в этом случае не будет иметь большого значения. Например, для модальности может потребоваться JDialog).   -  person c0der    schedule 14.09.2016
comment
Модальность подразумевает немедленное взаимодействие с пользователем. В противном случае он используется неправильно.   -  person RealSkeptic    schedule 14.09.2016
comment
Отличный вопрос! Да, SwingWorker - это то, что нужно для этого..   -  person Andrew Thompson    schedule 14.09.2016
comment
@AndrewThompson Спасибо за отзыв. Я ценю это.   -  person c0der    schedule 14.09.2016


Ответы (2)


а. showDiag работает в потоке GUI. Графический интерфейс будет полностью мертв, пока вы переводите поток графического интерфейса в спящий режим.

б. Да, используйте SwingWorker для длительных задач и используйте SwingUtilities.invokeLater() для отправки задач обновления графического интерфейса обратно в поток графического интерфейса. В качестве альтернативы реализуйте SwingWorker#done(), который представляет собой удобный метод, который запускается в потоке графического интерфейса пользователя после завершения задачи SwingWorker.

person Marko Topolnik    schedule 14.09.2016
comment
Спасибо. Я вижу, что вы говорите, запланируйте действие на потом. Использование SwingWorker для каждого процесса обновления довольно громоздко. Я бы предпочел реализовать это в обновленном (DiagB) объекте. Любой совет ? - person c0der; 14.09.2016
comment
@MarkoTopolnik Обратите внимание, что ОП использовал спящий режим для симуляции длительной задачи, поэтому их предложение SwingWorker внизу сообщения кажется лучшая стратегия для этого. - person Andrew Thompson; 14.09.2016
comment
@AndrewThompson Правда, я обновил, чтобы рекомендовать SwingWorker+invokeLater. - person Marko Topolnik; 14.09.2016

Основываясь на ответе Марко Топольника и комментарии Эндрю Томпсона, я исказил долгий процесс с помощью SwingWorker.
Это отлично работает:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingWorker;

public class DiagA extends JDialog  {

    private FrameB frame;
    private JButton btn;

    public  DiagA() {

        super();
        setTitle("main frame");
        setSize(200, 150);
        setLocation(400,400);

        btn = new JButton("Show Frame B");
        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {

                show2ndFrame();
            }
        });
        add(btn, BorderLayout.NORTH);
        setVisible(true);
    }

    void show2ndFrame() {

        if(frame == null) {

            frame = new FrameB();
            btn.setText("Exit");

        }else {

            System.exit(0);
        }

        doWork();
    }

    private void doWork() {

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

            @Override
            protected Void doInBackground() throws Exception {

                try {

                    for(int i = 1 ; i<=100 ; i++) {
                        //represents a long process
                        Thread.sleep(100);
                        frame.update(i);
                    }

                } catch (InterruptedException ex) {}
                return null;
            }
        };
        sw.execute();
    }

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

class FrameB extends JFrame  {

   JLabel label;

    public  FrameB() {

        super();
        setTitle("2nd frame");
        setSize(150, 100);
        setLocation(600,420);
        setLayout(new FlowLayout(FlowLayout.CENTER));
        getContentPane().setBackground(Color.YELLOW);
        label = new JLabel("0");
        add(label);
        setVisible(true);
    }

    void update(int progress) {

        label.setText(String.valueOf(progress));
    }
}
person c0der    schedule 16.09.2016