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.
Related
I have searched through the many questions saying wait until repaint has finished but I have never found an answer that I could really implement or understand!
I don't want the code to continue when the screen has been repainted!
I have a simple function:
public static void clearScreen()
{
panel.repaint();
}
I can tell you now that this works!
The only problem being that it does not wait to repaint so for example:
public static void blah()
{
drawSomething();
clearScreen();
drawSomething();
}
There is a good chance that both drawings would get clear from the screen. As you might be able to guess I do not want this!
I would just like to mention that all the separate functions work!
Requesting a repaint on a component will add a repaint event to the Event Dispatch Thread which is a separate background thread that will actually call paint() later.
Depending on what you are doing in drawSomething() may happen before or after the event dispatch thread has repainted. The best thing to do is to do all of your work on the event dispatch thread by doing your "draw something" inside a call to SwingUtilities.invokeLater. Which will run your code on the event dispatch thread. All processes on the event dispatch thread are executed in the order they are submitted (usually, some repaint requests are combined etc) so the logical order of calls are preserved.
Learn more about the Event Dispatch Thread in the Oracle Java Tutorial
Swing uses a passive painting system, that is, the screen is not updated on a regular bases, but is painted as is required (or more precisely, when ever the RepaintManager thinks it should be).
You can make requests that UI be updated by calling repaint, buts it's up to the RepaintManager to decide what and when that should occur. The RepaintManager will place a repaint event on the event queue which will be processed by the Event Dispatching Thread at some time in the future.
What this basically means, is that is generally not possible to know when a paint may occur (as painting may happen for all sorts of reasons).
Take a look at Painting in AWT and Swing for more details
You could...
Employee your own buffer, onto which you paint when ever you want, but is the painted to the screen independently. You can use a BufferedImage for this. The problem with this is you're going to have to monitor the changes to the container to ensure that the buffer is the right size
You could...
Implement your own queue, which you add "commands" to, which is then processed by your paint method
The problem with this is ensuring that you lock or copy the queue before processing it
So I'm making a graphical card game. Each card is a JPanel with a button and two images associated with it. I have a flip method, which is the first thing I call in my action listener when a card is clicked on.
public void flip()
{
if(b1.getIcon() == card2) b1.setIcon(card1);
else b1.setIcon(card2);
revalidate();
repaint();
}
However, for some reason the card doesnt flip (meaning the icons don't change) until some point after I call this method. For example, when I put a Thread.sleep after I call flip, I would assume that my program would pause after flip is completed, but it doesnt! It sleeps with the images not yet switched, and only switches them some at point after the sleep time has ended.
This is causing some major problems when I have a human play an AI in this card game, because AI events are happening before cards flip on the screen, even though I am calling flip() before I do any AI code. Can anyone clue me in as to what is going on here?
Repaint is a request to Swing to redraw not a call to redraw. Under the covers repaint() is posting a repaint job onto a queue of events. Along with that repaint job things like mouse moves, mouse clicks, keyboard activites, etc are posted there too. The Swing thread comes by and periodically executes jobs from that queue to redraw the UI, deliver mouse and keyboard events to components, etc. In fact the swing thread delivers these events quite frequently, but it just depends on how much work your UI is doing on that Swing thread. When it's out delivering mouse clicks, keyboard events, and redraws it can't read from that queue. So if your code takes a long time to repsond to any event the ability for swing to respond timely to new events is reduced or all together blocked.
That's why if you execute a blocking service call over the network with the Swing event dispatch thread your UI stops drawing until that call returns. This is why its important to move long running jobs like network calls off the Swing Thread, and use SwingUtilities.invokeLater() when the call comes back so you can update the UI on the Event thread. (invokeLater() does this by posting a job onto the Swing event queue we talked about above).
This is also why it's a terrible idea to call sleep on the Swing event thread. If you put that thread to sleep it can't service events from the queue. And the repaint() you requested isn't going to be done while the Swing thread is sleeping.
First remove your sleep call. Second. Don't write code that depends on painting to finish before executing more logic. Repaint and revalidate are special calls, and Swing will combine requests to repaint/revalidate so it doesn't redraw 10 times in a row (wasting vital cpu time).
Swing can't redraw your panel until you let the thread that called you leave the method it first called you back on. The sooner the thread leaves that method the sooner your repaint() will happen.
You haven't explained why you want the repaint to happen immediately so I can't give you any advice on how to structure your code so you don't care about it. But I'm telling you, you shouldn't care that the repaint hasn't happened yet.
Totally agree with #chubbsondubs. I just wanted to add that if your goal is to have some event happen some time after flip is called, consider using a swing timers. As you are painfully aware, calling Sleep does not have the desired effect. But if instead, you
create a timer for say 1 second
create an action to be performed when the timer fires
start the timer
then you can create the delay that you want without blocking the event dispatch thread.
public void flip()
{
if(b1.getIcon() == card2) b1.setIcon(card1);
else b1.setIcon(card2);
revalidate();
repaint();
javax.swing.Timer t = new javax.swing.Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
// do something interesting
}
});
t.repeats(false);
t.start();
}
I am combining Swing and Java3D together. Manipulations of swing components should be done using the Event Dispatcher Thread, while manipulations of Java3D components should be done in the BehaviourSchedulerThread.
Java3D renders the scene and then executes all of the behavior associated to the scene.
I have a MouseListener enabled on the Canvas3D. Events are posted into the AWT Event queue. I then want to modify the Java3D environment based on these events, so I use a special Behavior where I can post Runnable's to. This makes sure the Runnable's are executed during the Behavior cycle of Java3D (and do not modify anyting during the Render cycle).
Suppose some operations in the Behavior want to modify the Swing model. I then have to post a new Runnable to the EDT.
Is this the correct way to do it?
Using this technique, I experience a lot of problems on a mouse listener. I update a point in my Java3D model in the behaviour, and I update the swing GUI at the same time.
Update:
The problem can be more clearly defined as follows:
I have a JButton "spin cube" which has an ActionListener. Once the ActionListener is fired, it pushes the AWTEvent into a Java3D Behavior. Once the Behavior fires, it modifies the Scene graph and then modifies the JButton actionListener and text to become "Stop spinning".
Click on the JButton twice.
The first AWTEvent gets dispatched to SpinActionListener. The cube starts spinning and the JButton actionListener is modified to StopSpinningActionListener.
The second AWTEvent gets dispatched to StopSpinningActionListener. The cube stops spinning and the JButton actionListener is modified to SpinActionListener.
What actually happens is the following:
Click on a JButton twice. Both AWTEvents get dispatched to SpinActionListener. This creates a Runnable to execute inside the J3D Behavior.
The first AWTEvent starts a timer to spin the cube. It then posts a Runnable to the EDT to modify the button.
The second AWTEvent starts a timer to spin the cube. The cube will now spin twice as fast. It then posts a Runnable to the EDT to modify the button.
Obviously, I should not be depending on AWTEvent's getting processed sequentially. I cannot wait in the EDT for the behavior to fire, because any SwingUtilities.invokeAndWait() will then cause a deadlock.
Which WakeupCriterion is used to wake-up your special Behavior object?
Java 3D's source code includes the utility classes
com.sun.j3d.utils.behaviors.mouse.MouseBehavior/MouseRotate
which listens to Canvas3D's AWTEvents. One of two alternatives can be chosen:
MouseListener with WakeupOnBehaviorPost or
WakeupOnAWTEvent.
This code sample might be helpful.
Initiating a Swing component update from within the Behavior.processStimulus method via SwingUtilities.invokeLater shouldn't cause any problems.
Ive coded two Computers to play each other in Reversi, however when they play each other. The board only updates after the game has finished.
From some googling around I know its has something to do the AWT Event Thread, but I still have no idea how to force the JFrame to refresh.
My function works by changing the icons and then calling revalidate and repaint.
Any pointers would be wonderful.
If you start your AI game from an actionPerformed(), it is executed in EDT thread. You should move your logic (and your sleep()'s outside of EDT thread by starting a new Thread to allow Swing to repaint UI properly and post updates to UI as following:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myUI.update(); // repaint(), etc. according to changed states
}
});
Also consider use of javax.swing.SwingWorker, javax.swing.Timer and take a look at Concurrency in Swing.
Sounds like a threading issue - Event Thread is not being given a chance to execute. I would start with some sleep commands in the logic / action threads to give time for the UI to update.
Also, you could have an "updateUI" thread and run that on an invokeAndWait to force it to update the display. Call that after each move has been completed.
If you need to force the application to redraw, you should invoke the repaint() method on the component containing the game board. This should cause Swing to repaint the board.
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.