9

What is the right way to update the UI after doing some operations on Swing?

For example, after clicking a button, a method is called that may be almost instant or take some seconds. In fact, all the applicaton logic is done remotely through a web service, so it's normal to wait a little bit for the application to respond.

My eventhandler for a button may look like these:

myButton.addActionListener(new java.awt.event.ActionListener() {
   public void actionPerformed(java.awt.event.ActionEvent evt) {
       //callWebService();
       //do stuff
       //updateUI(); // <----- repaint? revalidate? what?
   }
});

My current implementation calls the updateUI method which internally call validate() and repaint() to the parent component that holds the UI. This works, but sometimes I can see the screen flickering. Am I doing it wrong? Is there a better way to do it?

Hectoret
  • 3,553
  • 13
  • 49
  • 56
  • http://en.wikipedia.org/wiki/SwingWorker – qwerty Feb 07 '11 at 11:58
  • As I understand it, this question is about updating the UI after doing something, not about doing stuff in another thread to keep the UI responsive. I don't quite get it why the execution time of the method was mentioned in the question, though. – Sergei Tachenov Feb 07 '11 at 12:15
  • I mentioned the execution time because it looks like Swing behaves a little bit different when trying to load components and the load takes too long, therefore maybe requiring to redraw the screen. – Hectoret Feb 07 '11 at 12:25
  • I'd still use the callback that the SwingWorker provides to do this - it's a model that's already provided and while the OP doesn't mention keeping the UI responsive as part of the question, it's good practice to do this anyway. – Michael Berry Feb 07 '11 at 13:51

4 Answers4

5

The right way would be to use SwingWorker, but if you want to do it manually you'll have to implement the following pattern:

@Override public void actionPerformed(java.awt.event.ActionEvent evt) {
  new Thread() {
    @Override public void run () {
      //callWebService();
      //do stuff
      SwingUtilities.invokeLater(new Runnable(){
        @Override public void run() {
          //updateUI(); // <----- repaint? revalidate? what?
        }
      });
    }
  }.start();
}

For the repaint/revalidate question, normally call revalidate() then repaint(). this is, of course, only valid for component that you manually draw. For components that you reuse, just call their value change methods.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Olivier Grégoire
  • 33,839
  • 23
  • 96
  • 137
  • Why is not using SwingWorker the "correct" way of doing this? It provides a nice, easy to use interface that handles all the callback stuff for you, and provides an easy way to update the UI when the task is complete. – Michael Berry Feb 07 '11 at 13:50
  • 3
    @berry, I thought that "without SwingWorker, which is the right way of doing this" was meant to be understood as "using Swing worker is the right way, but if you want to do it without using it..." – Sergei Tachenov Feb 07 '11 at 14:05
  • 1
    Ah, I see - a tad ambiguous though, I think it can be taken either way! – Michael Berry Feb 07 '11 at 14:06
  • 1
    Sergey is right. Sorry, no native English speaker over here ;) – Olivier Grégoire Feb 07 '11 at 22:44
4

I'd personally use SwingWorker for this, despite some of the other comments / answers:

  • Despite the fact keeping the UI responsive isn't part of the original question, it's good practice to do this anyway (I can't think of a single good reason to lock up the EDT with lengthy processing.)
  • It provides a done() method that can be implemented which will be executed on the EDT by default when the task is complete, saving the need for manually wrapping up things in invokeLater()
  • It's more extensible, providing the framework to allow information like progress to be added easily later if it's so desired.

I've seen a lot of SwingWorker hate in general recently, and I don't understand why. It's a nicely designed, extensible class specifically for purposes such as this that works well. Yes, you could wrap things up in threads and launch them and have them wrap other methods up in invokeLater(), but why reinvent the wheel when there's a better one available for free?

Michael Berry
  • 70,193
  • 21
  • 157
  • 216
1

Take the long running task to a different thread. Send events back to the AWT Event Dispatch Thread (EDT) to update the GUI with java.awt.EventQueue.invokeLater.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
0

You shouldn't normally have to do anything : if you use the swing components methods, or the methods of their model, the necessary events are fired and the GUI should update itself automatically.

This won't be the case if you define your own models (and forget to fire the necessary events, but then it's a bug in your models, and it should be fixed there.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • So if I call, say, the JSpinner.setValue() method in the GUI event processing thread, it should update the UI automatically? I remember it not doing it sometimes in one of my applications. If I moved the window or did something that would invalidate it (like temporarily obscuring it some other window), then it would update. Was that a bug in the JRE? – Sergei Tachenov Feb 07 '11 at 12:28
  • Yes, ti should update the GUI automatically (once there's nothing else to do in the event dispatch thread (EDT)), and if you make sure to call the method in the EDT. Can't say why it didn't work in one of your app, but it should. – JB Nizet Feb 07 '11 at 12:33