There is this article:
Someone jumped the queue!
Every now and then it appears that some swing events are processed in the incorrect order in the Event Queue (and nothing gets my blood boiling like when someone cuts into a queue) resulting in strange behavior. This is best illustrated with a small code snippet. Read the snippet below and think carefully in what order you imagine events will take place.
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
repaint();
doSomething();
}
});
Most developers would image that repaint() method will result in a painting operation taking place before the doSomething() method call. However this is actually not the case, the call to repaint() will create a new paint event that will be added to the end of the Event Queue. This new paint event will only be processed (dispatched) after the current Action Event has completed. This means that the doSomething() method will be executed before the new Paint Event on the queue is dispatched.
The key point here is that calls to repaint() will create a new paint event that will be added to the end Event Queue and not processed immediately. This means that no events jump the queue (and my blood can remain at its correct temperature).
(source)
My question is, how can I force Swing to do the repaint(); BEFORE doSomething();?
Also, if there were calls to the repaint() method WITHIN the doSomething(); they would be executed only after doSomething(); is completed. Is there a way I can pause the doSomething(); mid-executin, then throw in the reapaint();, get done with it, and then resume doSomething();?
Only solution I have found so far is this(link), but it's not really practical...
Well, you and the author of the quoted article are missing the point here. "repaint" method calls simply inform the repaint manager that:
There is a component to repaint (on which you call "repaint")
It should be repainted with (x,y,w,h) clip (if you call "repaint" w/o specifying rect -it will be the whole component bounds, (0,0,w,h))
So it doesn't really matter when the repaint will occur since you might not even notice it if you are calling A LOT of repaints one by one for the same component. That is also why such consequent calls might get merged. Check this example:
private static int repaintCount = 0;
public static void main ( String[] args )
{
final JComponent component = new JComponent ()
{
protected void paintComponent ( Graphics g )
{
try
{
// Simulate heavy painting method (10 milliseconds is more than enough)
Thread.sleep ( 10 );
}
catch ( InterruptedException e )
{
e.printStackTrace ();
}
g.setColor ( Color.BLACK );
g.drawLine ( 0, 0, getWidth (), getHeight () );
repaintCount++;
System.out.println ( repaintCount );
}
};
component.setPreferredSize ( new Dimension ( 200, 200 ) );
JFrame frame = new JFrame ();
frame.add ( component );
frame.pack ();
frame.setLocationRelativeTo ( null );
frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
frame.setVisible ( true );
new Thread ( new Runnable ()
{
public void run ()
{
try
{
Thread.sleep ( 1000 );
}
catch ( InterruptedException e )
{
e.printStackTrace ();
}
System.out.println ( "Starting repaint calls" );
for ( int i = 0; i < 100000; i++ )
{
component.repaint ();
}
System.out.println ( "Finishing repaint calls" );
}
} ).start ();
}
This is the approximate output you will see (might vary depending on computer speed, Java version and lots of other conditions):
1
Starting repaint calls
2
3
4
5
6
Finishing repaint calls
7
8
"1" - the initial repaint when frame is displayed.
"2,3,4..." - other seven repaints occured due to the calls from a separate non-EDT thread.
"But i have called 100000 repaints, not 7!" - you will say. Yes, repaint manager merged those that were similar and in the same time in the repainting queue. That is made to optimize repaints and speedup the UI overall.
By the way, you don't need to call repaint from EDT since it doesn't perform any real painting and just queue your component update for future. It is already thread-safe method.
To summ up - there should be no situations when you really need to repaint the component right before doing some other action (that could also cause its repaint again). Just call the repaint when you need to repaint the component (with specified rectangle when possible) - repaint manager will do the rest. That works well unless you put some calculations inside the paint method which is totally wrong and might cause a lot of problems.
repaint() adds a new paint request to the end of the queue of the Event Dispatch Thread (EDT). So making several calls to repaint() within doSomething() will only repaint after doSomething() completes. (I assume doSomething() is always called from the EDT. Your code example calls doSomething() from insideactionPerformed which is always called from the EDT.)
The reason paint() requests are queued up is to decrease the number of times a component is painted. Queuing up repaint() requests allows several methods to mark different components as dirty so they can all be repainted at the same time in one expensive paint() operation.
If you really want to force a repaint immediate, there are methods like paintImmediately(Rectangle r) and paintImmediately(int x, int y,int w,int h)but you will have to know the dimensions to repaint.
You can also always call paint(Graphics g) yourself if you have a reference to the current Graphics that the Swing component is using. (You can also use this technique to create your own graphics object from a Image object, if you want to take a screenshot and write it a picture file).
Related
I implemented a JFrame that contains some JLable's. I would like to change their appearance once they are clicked. The appended code should do so. In fact: It does not. Taking the same code and putting it into the run of an inner Thread-class does the job. The inner Thread-instance inverts the clicked JLable twice.
Can anybody give me a hint why the mouseClicked-method seems not to be able to affect the clicked JLable's appearance?
#Override
public void mouseClicked(MouseEvent e) {
if (clickable) {
for (Position p : positions.keySet()) {
JLabel lable = positions.get(p);
if (lable == e.getComponent()) {
pickedPosition = p;
LOGGER.info(pickedPosition + " pressed");
synchronized (lable) {
// store old colors
Color obg = lable.getBackground();
Color ofg = lable.getForeground();
// invert them
Color nbg = new Color(255 - obg.getRed(), 255 - obg.getGreen(), 255 - obg.getBlue());
Color nfg = new Color(255 - ofg.getRed(), 255 - ofg.getGreen(), 255 - ofg.getBlue());
// set them
lable.setOpaque(true);
lable.setForeground(nfg);
lable.setBackground(nbg);
// wait a while
try {
lable.wait(WAIT_WHILE_INVERTING_MS);
}
catch (InterruptedException i) {
LOGGER.warn(i.getMessage());
}
// switch back to initial
lable.setBackground(obg);
lable.setForeground(ofg);
}
e.consume();
}
}
}
}
There is no need for the synchronized block of code. All code executed from the event code will execute on the Event Dispatch Thread (EDT). Since you should always update the properties of components on the EDT you don't need to worry about other threads updating the component.
It looks like you want to temporarily change the Color of the label. The problem is the wait() method will block the EDT and prevent the GUI from repainting itself.
You can either:
Use a SwingWorker to start a Thread and then sleep for a period of time. Then when the worker is finished you can restore the color of the label. See Concurrency for more information and examples.
Use a Swing Timer to schedule the changes. See How to Use Swing Timers for more information.
I want to make an animation using java swing. I have a for loop that changes the location of an image slightly. My question is How can I make the each loop execution takes the specified amount of time?.
Its what I've done but I don't know how to use wait() and don't know is there a better way.
// inside of a MyJPanel class extending JPanel
for (int i = 0; i < FJframe.FRAMES; i++){
long start = System.currentTimeMillis();
animationFrame = i;
this.repaint();
long end = System.currentTimeMillis();
long remainder = (end - start);
System.out.println(remainder);
try {
this.wait(200 - remainder);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Edit
here my overrided Jpanel PaintComponent():
//some drawing
drawChanges(g, getDelta(), animationFrame, FJPanle.FRAMES);
And inside drawChanges(Graphics g, ArrayList deltas, int frame, int frames):
// a switch_case with cases similar to this one.
case AGENT_ATTACK:
// tu phase badi animation e kill
//drawImage(g2d, map[delta.getCell().x][delta.getCell().y], attacker);
source = map[delta.getSource().x][delta.getSource().y];
dest = map[delta.getDestination().x][delta.getDestination().y];
distanceX = dest.getCenter().x -
source.getCenter().x;
distanceY = dest.getCenter().y -
source.getCenter().y;
if (counter < frames / 2){
g2d.drawImage(ImageHolder.attacker, source.getBounds().x + (int)(((float)counter/frames) * distanceX),
source.getBounds().y + (int)(((float)counter/frames) * distanceY),
null);
}
else{
g2d.drawImage(ImageHolder.attacker, dest.getBounds().x - (int)(((float)counter/frames) * distanceX),
dest.getBounds().y - (int)(((float)counter/frames) * distanceY),
null);
}
break;
I want each loop takes, for example, exactly 200 miliseconds. How can I achieve this?
Look into using a Timer. For example, the scheduleAtFixedRate() method.
Probably not an acceptable answer, but too long for a comment:
There are several options, depending on the actual intention. The pattern that you described is not uncommon for a "simple game loop". In this case, a code that is similar to the one that you posted is run in an own thread, and regularly triggers a repaint() in order to paint the updated game state. In your case, it seems that only the animationFrame variable is increased. For such a simple action, the alternatives that have already been mentioned may be sufficient:
As suggested by whiskeyspider in https://stackoverflow.com/a/21860250/ : You could use a java.util.Timer with a TimerTask that only updated the animationFrame
Alternatively, you could use a javax.swing.Timer whose ActionListener updates the animationFrame. This might be advantageous here, because you can be sure that the update of the animationFrame happens on the event dispatch thread. Thus, the update of this variable can not interfere with its usage in the painting method, for example
But as mentioned in the comments: An own thread that executed the code that you already posted (with the minor adaptions) is also feasible and not "wrong" per se - but note that in this case (similar to when using a java.util.Timer) you might have to take care of the synchronization on your own
EDIT: Based on the edited question: Similar to what I expected, you are using the animationFrame in your painting method. The crucial point here are the details about how the animationFrame variable is used. For example, if your paintComponent method looks like this
class MyJPanel extends JPanel {
private int animationFrame = 0;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
drawChanges(g, getDelta(), animationFrame, FJPanle.FRAMES);
drawSomethingElse(g, animationFrame, ...);
drawEvenMore(g, animationFrame, ...);
}
...
}
then it may happen that the value of animationFrame is changed (by another thread, possibly the java.util.Timer thread) while the paintComponent method is executed. That means that drawChanges and drawSomethingElse may receive different values for animationFrame. This may cause rendering artefacts (misplaced images, tearing between tiles etc).
This could either be avoided by using a javax.swing.Timer (because then, the updates of animationFrame will be done on the same thread as the one that executes paintComponent), or by making sure that all painting operations use the same value of animationFrame - roughly like this:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int animationFrameUsedForAllPainting = animationFrame;
drawChanges(g, getDelta(), animationFrameUsedForAllPainting , FJPanle.FRAMES);
drawSomethingElse(g, animationFrameUsedForAllPainting , ...);
drawEvenMore(g, animationFrameUsedForAllPainting , ...);
}
But apart from that, there is not sooo much difference between the aforementioned approaches in this case. So for simplicity, you could use the javax.swing.Timer (or the java.util.Timer when you make sure that the update is "thread safe" regarding the painting operations).
I'm having a problem I'm making a pool game and I need the ballos to react when I simulate a hit, the program works like this, you click the direction and power to hit the ball and the click go, the go button is in the GUI class where my labels are created, the button calls a method from my main class that recieves the parameter and then with a while in it, changes the X and Y of the ball till the power is reduced to 0 and then stops, the code is working, but the ball moves until the while stops. So the while works and when the power int is 0 the while goes out and then the new X,Y are painted.
This is the funcion that the button calls, the button sends all the parameters
public void golpe(int pbola, int pvelocidad, String pdireccion, JLabel[] listalabels) throws InterruptedException{
listabolas[pbola].setVelocidad(pvelocidad);
listabolas[pbola].setDireccion(pdireccion);
while (listabolas[pbola].getVelocidad() > 0) {
moverBola(pbola, listalabels);
//System.out.println(listabolas[pbola].getPosX());
//System.out.println(listabolas[pbola].getPosY());
Thread.sleep(500);
//This line is supposed to change the X and Y of the object over and over
//but only does it till the end
listalabels[pbola].setLocation(listabolas[pbola].getPosX(), listabolas[pbola].getPosY());
}
}
Here is the function moverbola(), only copied one "if" so that the code doesn't look to big
private void moverBola(int pbola, JLabel[] listalabels) {
if (listabolas[pbola].getDireccion().equals("SE")) {
int pposX = listabolas[pbola].getPosX();
listabolas[pbola].setPosX(pposX + 1);
int pposY = listabolas[pbola].getPosY();
listabolas[pbola].setPosY(pposY + 1);
}
Swing is a single threaded framework. That is, all interactions with UI are expected to occur from within a single thread, known as the Event Dispatching Thread.
Any action that blocks this thread, will prevent the EDT from updating the screen or processing any new events.
Your while-loop is blocking the EDT, preventing it from painting any updates until after the while-loop is completed.
Take a look at Concurrency in Swing for more details.
There are a number of approaches you could take...
You could use a Thread, but this causes problems as you need to ensure that any changes you make to the UI are re-synced back to the EDT and this can become messy...
For example
You could use a javax.swing.Timer that ticks at a regular interval and you would update any internal parameters from within it's assigned ActionListener. Because the tick events occur within the EDT, it is save to update the screen from within it.
For example
You could use a SwingWorker to run the task in the background. It has methods for re-syncing updates back to the EDT, but might be a little over kill for your purposes...
Updated with a possible Timer example
Caveat- It is very hard to produce a reasonable example with only a code snippet, but, something like this might work
public void golpe(final int pbola, int pvelocidad, String pdireccion, final JLabel[] listalabels) throws InterruptedException{
listabolas[pbola].setVelocidad(pvelocidad);
listabolas[pbola].setDireccion(pdireccion);
Timer timer = new Timer(40, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (listabolas[pbola].getVelocidad() == 0) {
((Timer)evt.getSource()).stop();
} else {
moverBola(pbola, listalabels);
}
}
});
timer.setRepeats(true);
timer.start();
}
I have a program selection tool that i made. it opens a JFrame with 17 buttons, 15 of which are customizable, and they get their text from a .txt document located in the C: drive. when i click the assign button, it opens a JFileChooser to select a file to open when the button is clicked. You then select a button to change, and then type the text you want displayed by the button. After that the program rewrites the .txt file and updates the buttons. here is the code for updating:
public static void restart() {
start.assignButtonActions();
start.assignButtonText();
start.paint(graphics);
}
public void assignButtonActions() {
/**
* assign button actions
*/
for (int i = 0; i < buttonAction.length; i++) {
buttonAction[i] = io.readSpecificFromHD("C:\\ButtonActions.txt", i
+ 1 + actionButton.length);
}
}
public void assignButtonText() {
for (int i = 0; i < actionButton.length; i++) {
/**
* set button text
*/
actionButton[i].setText(io.readSpecificFromHD(
"C:\\ButtonActions.txt", i + 1));
}
}
public void paint(Graphics g) {
g.drawImage(getImage("files/background.png"), 0, 0, FRAMEWIDTH,
FRAMEHEIGHT, null);
refresh();
}
public void refresh() {
graphics.drawImage(getImage("files/background.png"), 0, 0, FRAMEWIDTH,
FRAMEHEIGHT, null);
for (int i = 0; i < actionButton.length; i++) {
actionButton[i].repaint();
}
assignButton.repaint();
helpButton.repaint();
}
Thats all the code that is required for this question i believe. The problem is, after the method restart() is called, the background is there, with a white square around the buttons, with it being white inside the square. not really a major problem, but really incredibly annoying and pretty unprofessional. At first i thought it was that the buttons were resizing after the background is painted, so i made it so that the refresh runs twice each time its called. didnt help one bit.
EDIT:
I fixed the problem. I took hovercraft's answer and modified what i learned a little bit. all i had to do was modify the restart() method to:
public static void restart() {
start.assignButtonActions();
start.assignButtonText();
start.repaint();
}
because the repaint(); repaint the whole component which was what hovercraft said. Thank you a ton everyone! hope this helps future questions.
You appear to be handling your Swing graphics incorrectly by calling paint(...) directly and trying to use a Graphics object outside of a JComponent's paintComponent(...) method. Don't do this, as all the Swing graphics tutorials will tell you (if you've not gone through some of them yet, you will want to do this soon). Instead do all graphics within a JComponent's (such as a JPanel's) paintComponent(...), call the super's method first, and use the Graphics object provided by the JVM in the paintComponent's method parameter.
Edit
Tutorial links:
The introductory tutorial is here: Lesson: Performing Custom Painting.
The advanced tutorial is here: Painting in AWT and Swing.
I'm thinking that you'll have to re-write most of your graphics code. Changes you should make:
Draw only in a JPanel or other JComponent-derived class, not in a JFrame or other top-level window.
Draw in your class's paintComponent(...) method.
Place an #Override annotation just above your paintComponent(...) method to be sure that you are in fact overriding the super method.
Call the super's paintComponent(...) as the first line (usually) of your paintComponent(...) override method.
Use the Graphics object passed into this method by the JVM.
Do not use a Graphics object obtained by calling getGraphics() on a component (with rare exceptions).
Do not give your class a Graphics field and try to store the Graphics object in it. The Graphics objects given by the JVM do not persist and will quickly become null or non-usable.
Do not call paint(...) or paintComponent(...) directly yourself (with rare exceptions -- and your current code does not qualify as one of the exceptions, trust me).
You will likely not need to call repaint() on your JButtons
I have an application which opens multiple JIFs, But I only want to create a single instance of the JIF, so I use these function to check that, and I use dispose to close the JIF after a key is pressed(JDesktopPane.getSelectedFrame().dispose()). However after 2-3 successive disposes, it doesn't create a new JIF? Am I doing anything wrong over here?
public static void setInternalFrame(final JInternalFrame internalFrame) {
log.debug("CurActiveInternalFrame " + ShoppyPOSApp.getCurrentActiveInternalFrame(), null);
log.debug("Incoming internalFrame " + internalFrame, null);
boolean isFrameFound = false;
try {
// Have a check whether the DesktopPane contains the internal Frame
// If yes bring it to front and set the focus
for (int i = 0; i < ShoppyPOSApp.frame.mainDesktopPane.getAllFrames().length; i++) {
if (ShoppyPOSApp.frame.mainDesktopPane.getAllFrames()[i].getClass() == internalFrame.getClass()) {
isFrameFound = true;
}
}
if (!isFrameFound) {
internalFrame.setVisible(true);
internalFrame.setLocation(
ShoppyPOSApp.frame.mainDesktopPane.getWidth()/ 2 - internalFrame.getWidth() / 2,
ShoppyPOSApp.frame.mainDesktopPane.getHeight() / 2 - internalFrame.getHeight() / 2
);
ShoppyPOSApp.frame.mainDesktopPane.add(internalFrame);
}
internalFrame.setSelected(true);
} catch (Exception e) {
log.debug(e.toString(), null);
}
}
You are comparing the classes of your input parameter and your desktops internal frames in your for loop. This will always be true as your parameter is an instance of JInternalFrame and the getAllFrames method returns an array of JInternalFrames. Why not just do a regular comparison? :
ShoppyPOSApp.frame.mainDesktopPane.getAllFrames()[i] == internalFrame
I would recommend using HIDE_ON_CLOSE as your default close operation on the frames and using setVisible(false) in your key listener instead of dispose(). When frames are disposed they are closed and you should not try and reuse a frame after is has been closed. If you just hide the frame it will still be a child of the desktop pane so you shoud add a call to setVisible(true) when you find a frame in your setInternalFrame method.
It sounds like you're getting inconsistent behaviour (you say it fails after two or three disposes). This suggests to me that you have an event thread problem. Is your setInternalFrame being called on the event thread? Are you familiar with the Event Dispatch Thread and are you using it correctly?
I don't think dispose is doing what you mean for it to do. dispose gets rid of the operating system "peer" of your frame. But if you intend to show that frame again, then you shouldn't throw away its underpinnings!
I'd go with setVisible(false) on the JIF to hide it. Then you can re-activate it with setVisible(true).