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?
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.
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.
Related
My Problem:
I have a long executing task in my application (Needs to fetch data from the web), and I'm trying to display a loading screen with an animation (Just a rotating circle, no progress bar needed) while the task is being executed
What I have done so far:
I created the design for the loading screen in a panel and added it to a JFrame and tried to call instantiate the JFrame before the the code for the long process, and then dispose it after the process is done, like this:
LoadingFrame frame = new LoadingFrame();
//Long Process
Wiring wiring = new Wiring(node.source);
wiring.generateScopeForTargetNode();
// close() calls setVisible(false) and then dispose()
frame.close();
However, the frame did not get repainted until the task was done and all I received was a blank box, and in the end it didn't get disposed.
I searched SO for the problem, and found that it has to do with Threads and concurrency (Which I am unfamiliar with) and found suggestions to use JDialog instead of JFrame, so I followed the suggestion. What I ended up with is this:
The JDialog loads and gets painted but the animation does not play
The JDialog does not get disposed at the end
What I need Help with:
I have tried to search more for the problem and found suggestions that I should use SwingWorker to run one task in a thread and the animation in another if I have understood correctly. However I am unfamiliar with threads and with SwingWorker and need help in creating a simple SwingWorker instance that achieves what I'm trying to do
Use a SwingWorker "to fetch data from the web" in the background. publish() interim results as they arrive. Your implementation of process() can then safely update a view component's model on the event dispatch thread. With even modest granularity, the user will start seeing data instead of an uninformative animation. This complete example updates the TableModel of a listening JTable, but the Document of a listening JTextComponent would work as well.
I have a JPanel with some JButtons on it. When the JButtons are clicked, an event handler is invoked. Within this event handler, I would like the capability of having the JPanel repaint multiple times. There is a lot of processing that occurs in this event handler over the course of several seconds, and I need to be able to update the JPanel to display incremental updates to the user. However, when I call repaint() on the JPanel within the event handler, nothing seems to happen. The JPanel waits to repaint until the event handler has returned.
I have tried using the repaint(long tm) method, but that doesn't seem to help. How do I get this desired behavior of repainting a JPanel multiple times from within an EDT?
Swing is single threaded, so event handlers and painting occur on a single thread (the EDT). If you have computation that takes time and attempt to do so on the EDT, no repainting (or anything else) can be performed. To overcome this, perform the long running tasks on a separate Thread or use a SwingWorker
As most all similar question answers will tell you -- use a SwingWorker to do the long-running task. Push updates to the GUI via the SwingWorker's publish/process method, and when the updates are passed into the GUI, repaint it. This way you avoid stomping on the Swing event thread and avoid freezing your program. Please check out Concurrency in Swing. Also have a look at my code in this answer to a similar question.
I have created a GUI having one JButton and some other components. When I click the button, some back end processing is involved which takes nearly 10-12seconds.
How should I represent it graphically?
Use a JProgressBar for displaying the progress of the task.
Put it inside a non-modal dialog that has a 'cancel' button (if the long running operation can safely be cancelled).
Be sure to obey the rules of the EDT by performing the long running task 'off the EDT', while the updates to the progress bar are done 'on the EDT'. The SwingWorker offers easy access to both.
I have a design related question that I am trying to find an answer to.
Here is the scenario.
Suppose that you want to do something expensive (time-consuming) as a result of user input (e.g. loading huge amounts data from some database, reading large files). The strongly recommended way is to do the time-consuming work in a separate thread and never ever block the EDT, or else the GUI will become unresponsive.
There are scenarios however when you should not provide inputs to the GUI unless the background task is finished. In my specific case, only after the background work is finished, I can determine which GUI elements should be visible and enabled/disabled. Only those GUI elements which should be visible and enabled should respond to the user inputs otherwise the behavior may be unpredictable in my specific case.
This is what I am doing to handle such a scenario.
Step 1: Before I am about to start a time-consuming operation.
Change the cursor to a busy cursor.
Add mouse listeners to the glasspane of component's top-level frame.
Make the glasspane visible so that it can receive mouse events. The glasspane doesn't do anything as a result of mouse inputs.
Step 2: Execute the time-consuming operation in a background thread. The background thread has a finally block that notifies the event thread when the job is finished (completed or aborted due to an error).
Step 3:
Switch the mouse cursor back to normal.
Remove listeners from the glass pane.
Make the glasspane invisible, so that mouse events go to their intended recipients.
Is this the correct approach to handle such situations?
What do you guys recommend?
SwingWorker can be used in this context. Related controls can be disabled when the background task is started and re-enabled in done(). In this related example, the run button is conditioned to toggle between "Run" and "Cancel".
Addendum: A back-port to Java 1.5 is available here.
As most Java programmers know, updates to Swing GUIs should only be done on the AWT event dispatching thread and the recommendation is that long-running processes be executed on a "worker" thread, with updates sent to the event dispatching thread using SwingUtilities.invokeAndWait() or SwingUtilities.invokeLater().
How do you stop the user from proceeding with the application while the long-running process is completed? Do you gray out the controls and then have the worker thread reenable them using the SwingUtilities calls mentioned above?
Is there a better alternative pattern?
I would consider 3 solutions :
disable the components of the panel : it's generally what I do. Unfortunately, Swing does not provide a simple way to disable a panel and all its children, but it is easy to do the recursion (see this other SO answer for that). Another problem is that some Swing components look the same when enabled and disabled (JList, for example)
hide the panel with a CardLayout : in a panel with a CardLayout, add 2 components. The first is the panel that hosts the components to inactivate, and the second is a panel showing a "loading" or "please wait" message. A simple JLabel in a Gridbaglayout does the trick. Then, you just have to switch from one to another. I use this technique for places where a result of a computation/request is to be displayed.
put some kind of component on top of the panel that consumes the mouse events : you can do it yourself with a LayeredPane, or you can use a dedicated utility. JXLayer can do that (I read that JXLayer will be included in Java 7, so this may become the 'standard' solution to this kind of problem).
There are several ways and the selection of which, mostly depends on the design and layout of your GUI.
Use a Progress Bar - Replace the panel or an area that you don't want a user touching with a progress bar. This will prevent you from having to deal with events you don't want yet, while still making it clear to the user that something is happening in the background.
Disable buttons and add a Wait Cursor - Use setEnable(false) while work is being done and nd possibly change the cursor to a Wait Cursor. This again makes it clear that an option is not available yet only for a temporary period.
Don't respond to events or throw up a GlassPane - This is less user-friend as it makes the application look unresponsive, however it can acceptable in some situations.
One way I have seen it done is to use Jframe.setGlassPane() and set a component that eats all events. You can also be creative and use flash kind of rotating-wait gif in your glasspane. But note that setting a glass pane may not be all you want. For more advanced requirements, you may have to play around with event-queues.