2

I have a JFrame with a CardLayout set as its layout manager. This has two JPanel subclasses in it. One is a panel, WordsLoadingPanel, which displays the text "Loading words..." and has a JProgressBar. The other has to actually load the words. This takes a while (about 10-14 seconds for 100 words; it's a pretty selective algorithm), so I want to assure the user that the program is still working. I made the loading algorithm in the panel fire a property change with firePropertyChange(String, int, int), and the WordsLoadingPanel is catching the change just fine - I know this because I added a listener for this event to perform a println, and it works. However, when I change the println to actually changing the JProgressBar's value, it doesn't do anything. I know I'm changing the value right, because if I set the value before the algorithm starts, it works, and it works on the last iteration of the loop. I'm guessing this is because my algorithm is eating the computing power and won't let JProgressBar update.

So, my question is: How do I make my algorithm wait for Swing (would this be the AWT Dispatching Thread?) to finish updating the progress bar before continuing? I've tried:

  • Thread.yield in each iteration of the loop
  • Thread.sleep(1000L) in each iteration of the loop, in a try/catch
  • putting everything in the loop in a SwingUtilities.invokeLater(Runnable)
  • putting only the CPU-intensive algorithm in a SwingUtilities.invokeLater(Runnable)

EDIT: To further support my hypothesis of the CPU-eating algorithm (sounds like a children's story…), when I set the JProgressBar to indeterminate, it only starts moving after the algorithm finishes.

Does anyone have any suggestions?

Thanks!

Savvas Dalkitsis
  • 11,476
  • 16
  • 65
  • 104
wchargin
  • 15,589
  • 12
  • 71
  • 110

2 Answers2

5

To do expensive operations in background, consider using the SwingWorker class. The documentation has examples on how to use it to do tasks that interact with the user interface in a separate thread, including progress display in JProgressBars.

If you have trouble understanding how the class works, consider:

  • SwingWorker is a generic class that takes two parameters: T, and V
  • The doInBackground method returns T and is executed in a separate thread.
  • Since Swing may only be manipulated in the Event Dispatch Thread, you may not manipulate Swing in doInBackground.
  • The process method takes a List<V> as a parameter and is called asynchronously on the Event Dispatch Thread.
  • The publish method takes V... arguments and sends them for processing in the process method.

In conclusion:

  • T is the type of the result of the computation, if any.
  • V is the type of the data needed to manipulate the user interface.
  • Your algorithm should run entirely in doInBackground.
  • The user interface should be manipulated in the process method.
  • Your algorithm should use publish to send data to the process method.
Matheus Moreira
  • 17,106
  • 3
  • 68
  • 107
  • I'm still quite confused about how the `SwingWorker` works, though. I have a `SwingWorker, Void>` with an `ArrayList doInBackground()` and a `void process(List chunks)`. What is the `List` argument? When is `process` called? I can't seem to understand that from the link you provided me (the documentation) or Wikipedia. Could you please explain a bit more? Thanks! – wchargin Oct 02 '11 at 01:34
  • Please also keep in mind that it's not one large task, it's one hundred small tasks (about 1/4 second each). – wchargin Oct 02 '11 at 01:52
  • 2
    @WChargin: If you want to work with the process/publish methods, then the SwingWorker's second generic parameter should not be Void but instead the type of object passed by process into publish. If you need more specific help, then post an [SSCCE](http://sscce.org), and we can help modify it for you to show you what you can try. – Hovercraft Full Of Eels Oct 02 '11 at 01:56
  • Oops: that should have stated passed by *publish* into *process*. – Hovercraft Full Of Eels Oct 02 '11 at 02:16
  • Updated answer. Hopefully it will make the workings of the class easier to understand. – Matheus Moreira Oct 02 '11 at 02:30
2

OK, I've solved it. For anyone who may have a similar problem, my solution was to change the method which begun the algorithm from executing it synchonously to asynchronously (with new Thread(Runnable).start). So, my code is now

EventQueue.invokeLater(new Runnable() {
    @Override
    public void run() {
        new Thread(new Runnable () {
            public void run () {
                window.keyboardTrainingPanel.initialize();                                  
            }
        }).start();
    }
});

I hope this can help someone! However, if there is a better way to do this, feel free to notify me.

wchargin
  • 15,589
  • 12
  • 71
  • 110
  • 2
    you don't want to update you UI from another thread directly. The swing framework was designed to be single threaded. What you want is using the Swingworker class which spawns a background thread for you but lets you post updates back to the ui thread from which you can safely update your progress bar. Go on line and read oracles Java tutorial, or any other source on multithreaded programming in java. – Savvas Dalkitsis Oct 02 '11 at 00:38
  • 1
    @HovercraftFullOfEels: Yes, I will do this; however, please don't downvote my answer - I posted this 15 seconds before Matheus posted his (you can tell by hovering over the "answered ... ago" text). – wchargin Oct 02 '11 at 01:10