Java swing progress bar from EDT problem
Asked Answered
I

2

6

This is for the swing experts out there. I have spent considerable time on this problem, so it is going to take me a few lines to explain the problem.

I have a standalone java swing application (java 6). In my application, I have a frame with a radio button group. I have a single action linked to all the buttons in the group. The action checks to see which radio button is selected and performs some work. The "work" involves some background computation as well as some painting in two other frames in my application. The background computation is multi-threaded.

I would like to display a progress bar when the user selects one of the radio buttons. However, when a radio button is selected, while the action to the radio button is happening, the progress bar never appears. I have tried jdialog type progress bars, glass panes, etc. None of them appear until the "work" is all completed. This seems to be because Swing does not finish painting the radio button until the "work" in the corresponding action is completed. And since the EDT only does one thing at a time, the progress bar dialog (or glass pane) is never displayed.

I then tried to use a SwingWorker to do all this "work". Start the progress bar (or activate a glass pane), start the SwingWorker and close the progress bar (or deactivate the glass pane) in the done() method for the SwingWorker. This seems to bring up the progress bar fine, but the painting which is part of the "work" is sometimes not completed, leaving me with some painting artifacts (the paintComponent method is pretty complicated, so do not want to reproduce here). The artifacts disappear if I resize the window. In fact, this happens if I use a class which extends Thread instead of SwingWorker too. This is all because Swing is not threadsafe and I am trying to do GUI work from a thread other than the EDT. I understand that part.

What do I do? "work" takes about 30 seconds and that seems too long to go without showing the user some kind of indication that the program is working. I have also tried changing the cursor to a wait cursor and have run into the same problems as above. The only thing that I can do is disable the frame and set the title of the frame to some text like "working..."

Anybody seen this problem before?

Inferential answered 12/8, 2010 at 23:33 Comment(0)
S
4

I think you are right to do the work in the SwingWorker thread, but you shouldn't be trying to do your painting there.

I'd be inclined to:

  • Have the ActionListener show() the progress bar, set off the swingworker then exit

  • Have the worker thread do the work, and periodically call repaint() on the progress bar component (this is guaranteed to be thread safe)

  • Progress bar has it's own paintComponent (which will be automatically called on the EDT). If necessary, this can read some variable that is updated by the worker thread to measure progress

  • When the worker thread finishes, have it call invokeLater() to run a final close down function on the EDT, which will hide the progress bar and do any other GUI-related cleanup / show a completion message to the user etc.

Somber answered 13/8, 2010 at 0:2 Comment(1)
How do I ensure that the EDT does not do any other painting until the work on the SwingWorker thread is completed? The work which can be done in the background has to be completed before my frames are painted again.Inferential
B
4

When you moved the work from the EDT to the swing worker (which was the right thing to do), it sounds like both the work and the painting moved to the swing worker. The painting should still happen on the EDT. You can achieve this by using SwingUtilities.invokeLater to invoke a repaint from the background thread, or by using the SwingWorker.publish(V...), which will take notifications from your worker thread and make them available on the EDT via the SwingWorker.process(V...) template method (which you override). Your process override can handle the intermediate notifications by repainting a portion of the screen, updating progress, or taking some other appropriate action as desired. Any UI changes done here will be visible without waiting for the rest of the work to complete.

Brezin answered 12/8, 2010 at 23:46 Comment(2)
Thanks, I got Mikera's solution to work, but your comment about having moved the painting to the EDT as well spurred the way I changed things!Inferential
Using SwingWorker.publish() on the worker thread is the way to go, I think.Goraud
S
4

I think you are right to do the work in the SwingWorker thread, but you shouldn't be trying to do your painting there.

I'd be inclined to:

  • Have the ActionListener show() the progress bar, set off the swingworker then exit

  • Have the worker thread do the work, and periodically call repaint() on the progress bar component (this is guaranteed to be thread safe)

  • Progress bar has it's own paintComponent (which will be automatically called on the EDT). If necessary, this can read some variable that is updated by the worker thread to measure progress

  • When the worker thread finishes, have it call invokeLater() to run a final close down function on the EDT, which will hide the progress bar and do any other GUI-related cleanup / show a completion message to the user etc.

Somber answered 13/8, 2010 at 0:2 Comment(1)
How do I ensure that the EDT does not do any other painting until the work on the SwingWorker thread is completed? The work which can be done in the background has to be completed before my frames are painted again.Inferential

© 2022 - 2024 — McMap. All rights reserved.