2

I have a method that can take a long time to be executed. Let's call it longTime();

This method is called from an action listener on a button.

I want to display a "Please wait.." message while this method is executing.

The problem is that the Jframe doesn't respond, it seems to be stuck or waiting for something.

Important note: The running time of longTime() can be different. The problem appears only when it takes more than 2-3 seconds.

I tried to do all of these in invokeLate but that didn't help.

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

Any ideas of how to fix this?

Maroun
  • 94,125
  • 30
  • 188
  • 241

5 Answers5

3

All lengthy tasks should occur in a separate thread, in order to prevent the GUI thread (Event Dispatch Thread) being blocked. I would suggest you look into using a SwingWorker object to execute the lengthy task and perform actions upon completion.

You can read more about this subject at http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html.

Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
3
  • there are two ways, by using JLayer (Java7) based on JXLayer (Java6) or Glasspane, use JLabel (with intermediate Icon) invoked

    1. from SwingWorker

    2. (easiest) from Runnable.Thread (output to the Swing GUI must be wrapped in invokeLater)

  • carefully with Concurency in Swing, all changes must be done on EDT

  • don't reinvent the wheel, code examples by @camickr

mKorbel
  • 109,525
  • 20
  • 134
  • 319
2

Highlighting mKorbel and Ducan's recommendations (+1 to both) with a simple example...

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

    }

}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
1

You have to spawn another thread and run your longTime() task in another thread. Calling invokeLater() still runs it in the UI thread, which you don't want to block.

Consider following code:

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

It's possible that you want to update a data model from that newly spawned thread. That would be the right place to use SwingUtilities.invokeLater(). Don't update models in this new thread. Models for UI components must be updated in UI thread only. Invoke later does exactly that.

ATrubka
  • 3,982
  • 5
  • 33
  • 52
  • The problem is that I can't `dispose()` the frame from the Thread. (It is not visible in this scope) – Maroun Feb 11 '13 at 08:59
  • 1
    @MarounMaroun And nor should. All interactions with the UI should be made from within the context of the Event Dispatching Thread – MadProgrammer Feb 11 '13 at 09:00
  • @MadProgrammer So changing the frame to `final` is not considered a good solution in this case? – Maroun Feb 11 '13 at 09:01
  • @MarounMaroun, I made frame variable final. That enables you to access the variable from another thread. So you should be fine. It is considered a good solution, so don't worry. – ATrubka Feb 11 '13 at 09:05
  • 1
    @MarounMaroun How ever you access the reference to the frame is irreverent in this example, you should never interact with any UI component from any thread other then the Event Dispatching Thread. But the solution to the problem will come down to what it is you want to achieve, and would be achieve more easily using something like a `SwingWorker` – MadProgrammer Feb 11 '13 at 09:05
  • @MadProgrammer, not all interactions are to be made from EDT. Most of the methods are actually documented to mention whether they can be accessed from other threads or not. From what I know JFrame.dispose() can be safely invoked from other threads. Actually, that's not even Swing's method because it's implemented in Window.dispose(). – ATrubka Feb 11 '13 at 09:09
  • @ATrubka Actually, most of them are document as been **thread unsafe**. The probelm is, you have no idea what `dispose` might actually trigger, like a `WindowListener` for example, then you're invoking a chain of events which aren't been passed through the EDT... – MadProgrammer Feb 11 '13 at 09:12
  • @MadProgrammer, in fact WindowListeners are invoked in EDT as well as other UI listeners. Of course, one has to be cautious about invoking from non-UI thread, but there could be too much protection in one's application. For instance, using invokeLater to call JLabel.setText() is too much. In general, though, one should consider using invokeLater as I mentioned in the last portion of my answer. – ATrubka Feb 11 '13 at 09:30
1

Simply call longTime() in a new thread, e.g.

new Thread(){ public void run() {longTime();}}.start();
iTech
  • 18,192
  • 4
  • 57
  • 80