Отображение сообщения от ActionListener до и после функции, которая занимает много времени

У меня есть метод, выполнение которого может занять много времени. Назовем его longTime();

Этот метод вызывается из прослушивателя действий на кнопке.

Я хочу отображать сообщение "Please wait.." во время выполнения этого метода.

Проблема в том, что Jframe не отвечает, кажется, что он застрял или чего-то ждет.

Важное примечание. Время работы longTime() может быть разным. Проблема появляется только тогда, когда это занимает более 2-3 секунд.

Я пытался сделать все это в invokeLate, но это не помогло.

SwingUtilities.invokeLater(new Runnable() {
  @Override
  public void run() {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
    JLabel label = new JLabel("Please wait...");
    label.setFont(new Font("Serif", Font.PLAIN, 25));
    frame.getContentPane().add(label, BorderLayout.CENTER);
    frame.setLocationRelativeTo(null);
    frame.setUndecorated(true);
    frame.pack();
    frame.setAlwaysOnTop(true);
    frame.setVisible(true);
    try{            
      longTime();  //HERE IS THE FUNCTION THAT TAKES A LONG TIME  
    }catch(Exception e){   }
    frame.dispose();  //AFTER THE LONG FUNCTION FINISHES, DISPOSE JFRAME        
  } 
});

Есть идеи, как это исправить?


person Maroun    schedule 11.02.2013    source источник


Ответы (5)


Вам нужно создать еще один поток и запустить задачу longTime() в другом потоке. Вызов invokeLater() по-прежнему запускает его в потоке пользовательского интерфейса, который вы не хотите блокировать.

Рассмотрим следующий код:

    final JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
    JLabel label = new JLabel("Please wait...");
    label.setFont(new Font("Serif", Font.PLAIN, 25));
    frame.getContentPane().add(label, BorderLayout.CENTER);
    frame.setLocationRelativeTo(null);
    frame.setUndecorated(true);
    frame.pack();
    frame.setAlwaysOnTop(true);
    frame.setVisible(true);

    new Thread(new Runnable() {
        @Override
        public void run() {
        try{            
            longTime();  //HERE IS THE FUNCTION THAT TAKES A LONG TIME  
        }catch(Exception e){   }
            frame.dispose();     //AFTER THE LONG FUNCTION FINISHES, DISPOSE JFRAME     
        } 
        }).start();

Возможно, вы захотите обновить модель данных из этого вновь созданного потока. Это было бы правильное место для использования SwingUtilities.invokeLater(). Не обновляйте модели в этой новой теме. Модели для компонентов пользовательского интерфейса должны обновляться только в потоке пользовательского интерфейса. Invoke Later делает именно это.

person ATrubka    schedule 11.02.2013
comment
Проблема в том, что я не могу dispose() кадр из Thread. (Это не видно в этом прицеле) - person Maroun; 11.02.2013
comment
@MarounMaroun И не должен. Все взаимодействия с пользовательским интерфейсом должны осуществляться в контексте потока диспетчеризации событий. - person MadProgrammer; 11.02.2013
comment
@MadProgrammer Значит, изменение кадра на final в этом случае не считается хорошим решением? - person Maroun; 11.02.2013
comment
@MarounMaroun, я сделал окончательную переменную кадра. Это позволяет вам получить доступ к переменной из другого потока. Так что вы должны быть в порядке. Это считается хорошим решением, так что не волнуйтесь. - person ATrubka; 11.02.2013
comment
@MarounMaroun Как бы вы ни обращались к ссылке на фрейм, в этом примере это непочтительно, вы никогда не должны взаимодействовать с каким-либо компонентом пользовательского интерфейса из любого потока, кроме потока диспетчеризации событий. Но решение проблемы будет сводиться к тому, чего вы хотите достичь, и его будет легче достичь, используя что-то вроде SwingWorker - person MadProgrammer; 11.02.2013
comment
@MadProgrammer, не все взаимодействия должны выполняться из EDT. Большинство методов на самом деле задокументированы, чтобы указать, можно ли получить к ним доступ из других потоков или нет. Насколько я знаю, JFrame.dispose() можно безопасно вызывать из других потоков. На самом деле это даже не метод Swing, потому что он реализован в Window.dispose(). - person ATrubka; 11.02.2013
comment
@ATrubka На самом деле, большинство из них задокументированы как небезопасные для потоков. Проблема в том, что вы понятия не имеете, что на самом деле может вызвать dispose, например, WindowListener, тогда вы вызываете цепочку событий, которые не прошли через EDT... - person MadProgrammer; 11.02.2013
comment
@MadProgrammer, на самом деле WindowListeners вызываются в EDT, как и другие прослушиватели пользовательского интерфейса. Конечно, нужно быть осторожным при вызове из потока, отличного от пользовательского интерфейса, но в приложении может быть слишком много защиты. Например, использование invokeLater для вызова JLabel.setText() — это слишком. Однако в целом следует рассмотреть возможность использования invokeLater, как я упоминал в последней части своего ответа. - person ATrubka; 11.02.2013

Все длительные задачи должны выполняться в отдельном потоке, чтобы предотвратить блокировку потока GUI (Поток отправки событий). Я бы посоветовал вам изучить использование SwingWorker объект для выполнения длительной задачи и выполнения действий по завершении.

Подробнее об этом можно прочитать на странице http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html.

person Duncan Jones    schedule 11.02.2013

  • есть два способа: используя JLayer (Java7) на основе JXLayer (Java6) или Glasspane, используйте JLabelintermediate Icon) вызванным

    1. от SwingWorker

    2. (самый простой) из Runnable.Thread (вывод в графический интерфейс Swing должен быть заключен в invokeLater)

  • осторожно с параллельным доступом в Swing, все изменения должны быть сделаны в EDT

  • не изобретайте велосипед, используйте примеры от @camickr

person mKorbel    schedule 11.02.2013

Выделение рекомендаций mKorbel и Ducan (+1 к обоим) на простом примере...

public class RunLongTime {

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

    public RunLongTime() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public interface WorkMonitor {

        public void updateProgress(float progress);
        public void done();

    }

    public class TestPane extends JPanel implements WorkMonitor {

        private JLabel message;
        private JButton button;

        public TestPane() {
            message = new JLabel("Click to get started");
            button = new JButton("Start");
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            gbc.insets = new Insets(2, 2, 2, 2);
            add(message, gbc);
            add(button, gbc);

            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    button.setEnabled(false);
                    message.setText("Getting started...");
                    BackgroundWorker worker = new BackgroundWorker(TestPane.this);
                    worker.execute();
                }
            });
        }

        @Override
        public void updateProgress(float progress) {
            message.setText("Please wait..." + NumberFormat.getPercentInstance().format(progress));
        }

        @Override
        public void done() {
            message.setText("All done...");
            button.setEnabled(true);
        }

    }

    public class BackgroundWorker extends SwingWorker<Void, Float> {

        private WorkMonitor monitor;

        public BackgroundWorker(WorkMonitor monitor) {
            this.monitor = monitor;
        }

        @Override
        protected void done() {
            monitor.done();
        }

        @Override
        protected void process(List<Float> chunks) {
            monitor.updateProgress(chunks.get(chunks.size() - 1));
        }

        @Override
        protected Void doInBackground() throws Exception {
            for (int index = 0; index < 1000; index++) {
                Thread.sleep(125);
                publish((float)index / 1000f);
            }
            return null;
        }

    }

}
person MadProgrammer    schedule 11.02.2013

Просто вызовите longTime() в новом потоке, например.

new Thread(){ public void run() {longTime();}}.start();
person iTech    schedule 11.02.2013