Wait for AWT repaint to finish - java

I have a Frame that is all one color, but has some text in the corner. I set the color, actually read the color from the monitor, and then do some computations based on those measurements.
The problem is, calling repaint() causes the Frame to be painted after I do the measurements. I'm assuming this is due to repaint() delegating to the EDT, but I'm getting incorrect results due to the measurements occurring before/during the actual painting work.
My initial thought was to put a listener on paint completion, but I repaint to update the text much more frequently than I do for the color and I don't want to listen to those events. How can I wait for the actual painting task to finish before taking my measurement?

Amazing what you can find...
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
PaintEvent pe = (PaintEvent) event;
String type = "";
if (pe.getID() == PaintEvent.PAINT) {
type = "PAINT";
} else if (pe.getID() == PaintEvent.PAINT_FIRST) {
type = "PAINT_FIRST";
} else if (pe.getID() == PaintEvent.PAINT_LAST) {
type = "PAINT_LAST";
} else if (pe.getID() == PaintEvent.UPDATE) {
type = "UPDATE";
}
System.out.println(type + "; pe.UpdateRec = " + pe.getUpdateRect() + "; pe.component = " + pe.getComponent());
}
}, AWTEvent.PAINT_EVENT_MASK);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
Now, because repaint requests can come think and fast, I'd be tempted to place a small "delay" in that would fired shortly after the last request has completed...
private Timer updateTimer;
// ...
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
updateTimer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Update compulations here...
}
});
updateTimer.setRepeats(false);
updateTimer.setCoalesce(true);
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
updateTimer.restart();
}
}, AWTEvent.PAINT_EVENT_MASK);
}
}
The idea is to allow at least 250 milliseconds between the last repaint request and the start of your compilations. You might like to play around with these values a bit and see what suits you...
UPDATE
You could also try JComponent.paintImmediately
Paints the specified region in this component and all of its
descendants that overlap the region, immediately.
It's rarely necessary to call this method. In most cases it's more
efficient to call repaint, which defers the actual painting and can
collapse redundant requests into a single paint call. This method is
useful if one needs to update the display while the current event is
being dispatched.

I know this is an old thread, but in case someone is searching for an answer to this, SwingUtilities has a method called invokeAndWait(Runnable) that runs the task on AWT dispatching thread synchronously with your current thread. Note that this cannot be done if you need it to be done inside the Event dispatching thread.

Related

Changing a label during ActionEvent

I am trying to enable/disable a label when a button is pressed and i want to do this during the event and not after it. As you can see below, i try to enable/disable the two labels: lblKeyboard and lblGamepad.
They end up running after the "RemoteControl.run();" is executed but i want it to happen before that. Any way i can do that?
Thank you!
JButton btnGamepad = new JButton("Gamepad");
btnGamepad.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(cont_state == 0){
if(RemoteControl.findGamePad() == true){
cont_state = 1;
game_status = "on";
}
else{
game_status = "off";
key_status = "on";
JOptionPane.showMessageDialog(null, "Controller not found!");
cont_state = 0;
}
}
if(cont_state == 1){
System.out.println("CONNECTED GAMEPAD!");
lblKeyboard.disable();
lblGamepad.enable();
frame.repaint();
RemoteControl.run();
cont_state = 0;
}
}
});
ActionEvents are run on the EDT which is also responsible for painting. Once you change the labels state, Swing issues a request for repaiting the Label. The thing is that this request is posted on a queue and will be executed once the EDT is free and, as you can see, the EDT is busy running your code so no repainting for you! Depending on the nature of your code, you should consider using a SwingWorker or simply moving RemoteControl.run() to another Thread as in
new Thread(new Runnable() {
#override
public void run() {
RemoteControl.run();
}
}).start();
Code in an event listener executes on the Event Dispatch Thread (EDT) and the GUI can't repaint itself until all the code has finished executing. Read the section from the Swing tutorial on Concurrency for more information on the EDT.
Try wrapping your RemoteControl.run() code in a SwingUtilities.invokeLater(...). This will place the code at the end of the EDT, which might give Swing a changes to repaint the state of the two labels.
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
RemoteControl.run()
}
});
This assumes your code updates the GUI. If not, then just use a separate Thread.

Is there a way to set up two or more the event dispatch thread (EDT)?

Is Java capable of creating more than one EDT at a time?
I'm experimenting with setting up EDT and how it works in updating the content of a "heavy duty" panel with potentially a dozen of panels embedded inside and with hundreds of components altogether. Currently I have
public void run() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
panel.update();
}
});
}
I've looked at the following posts:
Measuring "busyness" of the event dispatching thread
How does the event dispatch thread work?
Java Event-Dispatching Thread explanation
http://en.wiki2.org/wiki/Event_dispatching_thread
and so forth.
I sort of understand that if there are, say a dozen of events, that an single EDT has to handle, Java already has an internal scheduling mechanism to group/prioritize these events.
According to http://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html
"This is necessary because most Swing object methods are not "thread safe": invoking them from multiple threads risks thread interference or memory consistency errors."
So what if I create a 2nd EDT with new Thread(new Runnable() { ... }.start() below?
Will java automatically merge the two EDTs back to one for fear of thread safety?
new Thread(new Runnable() {
public void run() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
panel.update();
}
});
}
}).start();
There can only be one Event Dispatch Thread!
But why would you even want to have more than one thread for this? Even for "heavy duty" panels with many components (in the application i am currently working on there must be 1000s of components) one EDT is enough. Remember you should not perform any tasks on the EDT that use a lot of CPU time. Otherwise you will block the EDT for update events and your GUI will become "sluggish" in responding to user input.
Also remember that all GUI components should be created and manipulated only from within the EDT because many components are not thread save. Ignoring this guideline may work for specific tasks but sooner or later you will get strange behavior and/or crashes!
The Swing GUI is single threaded. That single thread being the EDT. If you wanted to introduce a second EDT (and still have the GUI working) you would have to also rewrite a lot of the internal Swing code to account for the added complexity of thread safety.
Adding another EDT would introduce more complexity for an unknown amount of increase (or decrease) in performance.
The following applies in case one uses the Sun toolkit. I am not sure about other Java implementations (have not tested it).
One can have multiple EDT threads provided the toolkit is initialized from multiple threads from separate thread groups.
PoC code:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] argv) {
for (int i = 0; i < 5; i++) {
// the separate thread group is needed at least for the Sun toolkit
ThreadGroup tg = new ThreadGroup("Test Group " + i);
Thread t = new Thread(tg, new Runnable() {
#Override
public void run() {
startApp();
}
}, "Test " + i);
t.start();
}
}
private static void startApp() {
sun.awt.SunToolkit.createNewAppContext();
final JFrame frm = new JFrame(Thread.currentThread().getName()) {
#Override
public void setVisible(boolean b) {
super.setVisible(b);
System.out.println("Closed");
if (!b) dispose();
}
};
final JTextArea ta = new JTextArea();
frm.add(ta);
JButton btn = new JButton("Dialog");
// Showing a modal dialog will block only this frame
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(frm, "Test message from " + frm.getTitle());
}
});
frm.add(btn, BorderLayout.SOUTH);
frm.setPreferredSize(new Dimension(300, 300));
frm.pack();
frm.show();
Thread t = new Thread(new Runnable() {
int i = 0;
#Override
public void run() {
try {
while (true) {
i++;
if (!frm.isVisible()) break;
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
try {
ta.getDocument().insertString(0, "Test " + i + " " +
Thread.currentThread().hashCode() + // This is to show that we are actually on a different thread as invokeAndWait is static and one may suspect a different behaviour
"\n", null);
} catch (Throwable t) {
t.printStackTrace();
}
}
});
Thread.sleep(1000 + (int)(Math.random() * 500));
}
Thread.sleep(4000); // This is just to show that the deamon thread might not have ended before the toolkit calls System.exit
} catch (Throwable t) {
t.printStackTrace();
}
System.out.println("Thread " + Thread.currentThread().getName() + " exit");
}
});
t.setDaemon(true);
t.start();
}
}
The above code demonstrates opening 5 frames on separate EDTs. When each frame is closed and disposed the respective EDT ends. When all the frames are closed and disposed the app will exit (automatically).
Notes:
one should not use (mix) components created in one EDT from another EDT;
closing the modal dialog from the example has the side effect of not returning the focus to the parent frame (as one would expect) but to any of the others.
I use the above approach in order to measure the height of a wrap enabled JTextArea that is populated with a multitude of different texts that might take some time and during that time I do not want to block the main UI. The result (the measured heights) is used on the main UI.
Tested with Java 1.6.0_25 and 1.8.0_60.

java set delay to change imageicon

i'm trying to set a delay when a button is pressed to set an imageicon to a certain image then set another delay so that another image would be set, all of this by single click.
in other word :
click a button->set image->delay->set another image.
what i get in my code is the last state only "set another image".
also i don't want to use use timers, i want to use delays.
and here the part in my code i'm concerned about.
btnNewButton.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
lblNewLabel.setIcon(and);
sleeep(500);
lblNewLabel.setIcon(app);
}
});
and here is the delay function
void sleeep(int n)
{
try {
Thread.sleep(n);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
don't add MouseListener to JButton, nor for mouseClicked(), add ActionListener instead, btw all Mouse and Key events are implemented in JButton API and correctly
don't to use Thread.sleep(n); you have an issue with Concurency in Swing, use Swing Timer instead,
You should try executing the code that sets the image in the event dispatch thread using InvokeLater.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
lblNewLabel.setIcon(and);
}
});
sleeep();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
lblNewLabel.setIcon(and);
}
});

Blinking in JFrame Java

Hello guys I am doing a thread to update a ball over JFrame so I repaint the screen... and then paint the ball update its position .. and then draw the screen again ... draw the ball and the same cycle ... here is the code
private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {
Thread t = new Thread()
{
public void run()
{
while(true)
{
repaint();
b2.update(ob,2);
b2.paint(ob.getGraphics());
b2.setT(b2.getT() + 1);
try {
Thread.sleep(50);
} catch (InterruptedException ex) {
System.out.println("Error in Sleeping");
}
}
}
};
t.start();
}
but the problem is that I don't see the ball... the paint of the screen always overrides the ball and the ball is like down under the Jframe ..
If you want to have animations in Swing, the recommended class to use is the javax.swing.Timer . This class allows you to perform operations on the Event Dispatch Thread at regular intervals.
The Swing Timer tutorial
An animation example posted here on SO (which is linked in the Swing wiki here on SO btw)
Some General Rules
Swing is not thread safe, you should only ever update UI components from within the context of the Event Dispatching Thread.
You do not control the paint process, the repaint manager does. You can request updates to occur by calling repaint, but you should never call update and paint directly when trying to update the display.
The Graphics context used by the paint sub system is a shared resource and is not guaranteed to be the same between paint cycles, you should never maintain a reference to it. You should also not rely on the results from JComponent#getGraphics this method is capable of returning null.
An Example Solution
You have a number of options, depending on what you want to ultimately achieve.
You could use a SwingWorker, but given the fact that all your going to is enter an infinite loop and it would easier to use SwingUtilities#invokeLater then actually use the publish method, this approach would actually be more work.
You could also use a Thread, but you'd end up with the same problems as using a SwingWorker
The simpliset solution, for what you're presented, is actually a javax.swing.Timer
public class Blinky {
public static void main(String[] args) {
new Blinky();
}
public Blinky() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new BlinkyPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected class BlinkyPane extends JPanel {
private JLabel blinkyLabel;
private boolean blink = false;
public BlinkyPane() {
setLayout(new GridBagLayout());
blinkyLabel = new JLabel("I'm blinking here");
blinkyLabel.setBackground(Color.RED);
add(blinkyLabel);
Timer timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
blink = !blink;
if (blink) {
blinkyLabel.setForeground(Color.YELLOW);
} else {
blinkyLabel.setForeground(Color.BLACK);
}
blinkyLabel.setOpaque(blink);
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 100);
}
}
}
You can take a look at Swing Timer and Concurrency in Swing for more info
If you access GUI components outside the EDT (Event Dispatch Thread) then you might encounter strange problems, Contrary if you perform long running tasks in the EDT then also you will get problems.
Check this post for more info on GUI Threading in Java

Trigger event only after repaint in Java Swing?

I am making a simple board game in java, where I want to animate a dice roll. So I flash pictures of a dice like this:
public Timer roll_dice = new Timer(50, this);
...
public void actionPerformed(ActionEvent evt) {
if(roll_dice.getDelay() > 500){
roll_dice.setDelay(50);
roll_dice.stop();
movePiece();
}else{
roll_dice.setDelay(roll_dice.getDelay() + 50);
dice_panel.repaint(0);
}
}
}
movePiece(){
//do some more painting
}
So the die is going so show random numbers for a few times, and then slowly settle on a number. After that is done I would like to call the movePiece() method. However, as it is, the the repaint occurs sporadically and screws everything up so that movePiece() gets called before the dice roll is actually finished animating.
Does anyone have any ideas how I can call movePiece only after the final repaint has happened?
So the die is going so show random numbers for a few times, and then slowly settle on a number. After that is done I would like to call the movePiece() method. However, as it is, the the repaint occurs sporadically and screws everything up so that movePiece() gets called before the dice roll is actually finished animating.
What worries me here is why your painting is occurring sporadically -- it simply shouldn't be doing that, and perhaps that is what you need to fix. I wonder if you're reading in the images from the file each time you do the drawing or some other cause for slowing the drawing down. If you need more help regarding this issue, then you'll have to give us more information on how you do your painting. Regardless, you should avoid having program logic be dependent on painting as you don't have full control over when or even if painting will occur.
Rather than redrawing images and calling repaint(), why not simply put your rolling dice images into ImageIcons on program start up, and then in your Swing Timer, swap icons in a JLabel? Then stop your Timer when the delay gets long enough and in that if block, move your piece.
So, assuming that you have several dice, each can be displayed by a JLabel that is held in an array of JLabel called diceLabels, and the ImageIcons can be held in an array called diceIcons. Then you can do something like:
public void actionPerformed(ActionEvent e) {
if (roll_dice.getDelay() > 500) {
roll_dice.setDelay(50);
roll_dice.stop();
movePiece(); // I like this -- this shouldn't change
} else {
roll_dice.setDelay(roll_dice.getDelay() + 50);
// dice_panel.repaint(0);
for (JLabel dieLabel : diceLabels) {
int randomIndex = random.nextInt(diceIcons.length);
dieLabel.setIcon(diceIcons[randomIndex]);
}
}
}
I like your logic on when you call movePiece() and I think that this should remain unchanged.
You can call the rolling in another thread and join() the current thread to the rolling one. That way the main code will wait until the roll thread dies (finished rolling).
public void actionPerformed(ActionEvent evt) {
if(roll_dice.getDelay() > 500){
Thread rollerThread = new RollerThread();
rollerThread.start();
rollerThread.join();
movePiece();
}
else{
roll_dice.setDelay(roll_dice.getDelay() + 50);
dice_panel.repaint(0);
}
}
private RollerThread extends Thread
{
public void run(){
roll_dice.setDelay(50);
roll_dice.stop();
}
}
However, this might not work with the EDT - because repaints should be scheduled to the queue. Maybe you can shedule the event using the SwingUtilities.invokeAndWait():
public void actionPerformed(ActionEvent evt) {
Thread thread = new Thread(){
public void run(){
if(roll_dice.getDelay() > 500){
SwingUtilities.invokeAndWait(new Runnable(){
public void run(){
roll_dice.setDelay(50);
roll_dice.stop();
}
});
movePiece();
}
else{
roll_dice.setDelay(roll_dice.getDelay() + 50);
dice_panel.repaint(0);
}
}
};
thread.start();
}
Does anything change if you put that call to movePiece(); in a SwingUtilities.invokeLater(Runnable);?
if(roll_dice.getDelay() > 500){
roll_dice.setDelay(50);
roll_dice.stop();
SwingUtilities.invokeLater(new Runnable() {
public void run() { movePiece(); }
});
}
...

Categories