Continuing the series on developing applications in Java using SWT, I’d like to address the problem of implementing progress dialogs for long-running operations. The last time I was on the topic of SWT, I explained how I implemented a sash in my GUI.
This application processes potentially large datasets, which can be very time-consuming operations (anywhere from a few seconds to several minutes or more). The demo and early iterations of the project simply became unresponsive after the user chose a dataset, and remained unresponsive until the processing was complete. The obvious thing to do here is to implement a progress dialog that gives the user a visual indication that the application has not hung, and, ideally, provides the capability to cancel the operation.
This turned out to be a bit more difficult than I anticipated, so I thought I’d relate my experiences.
Phase 1: Modal Pop-up Dialog
This part is not difficult. To make things reusable, and to make the follow-on phases easier to implement, I created a DialogProgress class. The constructor accepts two strings (one for the dialog title, one for the text inside the dialog), and creates a new shell with a ProgressBar widget. I made it the ProgressBar widget private, and added an
update(finished, outOf) method to DialogProgress for updating the widget. The dataset processing routine just calls
update() each time it finishes processing an entry in the dataset (for example, invoke
update(5, 10) to indicate that you just finished processing the 5th entry in a dataset with 10 entries).
Of course, now that you have a progress dialog up and going, the next obvious step is to stick a cancel button on it.
Phase 2: Allow Canceling
Here’s the catch: adding a button to the progress bar dialog means that something needs to be listening for the mouse click. The dataset processing, then, should move into a separate thread, so that the main process can enter the
My initial thought was to have the main application start the thread and execute a
wait(). Then the thread would signal its completion by invoking
signal(). The problem is that the main application does not enter the display.readAndDispatch() loop because it is busy waiting… the new “cancel” button can’t be clicked.
The most elegant solution that I came up with was to provide the ability to register processingComplete and processingCanceled listeners with a dataset processing object. The main process instantiates a dataset processing object, registers a processingComplete listener and a processingCanceled listener with it, and then invokes the
run() method of the object to start the thread. There is no need to wait anymore; the main process simply returns to the
Now, if the user clicks the “cancel” button, the dataset processing object will invoke the processingCanceled listener. If the dataset processing finishes without interruption, the processingComplete listener will be invoked just before the thread exits, and the main process will know to update the UI with the results of the processing.
Phase 3: Eliminating the Dialog: Asynchronous Updating
I haven’t actually gotten to this point with this application yet, but I believe this will be the next iteration. I plan to eliminate the modal progress dialog, and move the progress bar to the status bar on the bottom of the main shell. This way, the dataset can be shown as it is loaded, meaning that the user can begin reviewing the first entries in the dataset while the later entries are still being loaded. I’ll post again if I ever get around to implementing this iteration!