Currently I use this code:
public void mouseDragged(MouseEvent e) {
try {
repaint();
getGraphics().drawImage(TreeDrag.obj.getImg(), getMousePosition().x, getMousePosition().y, null);
} catch (HeadlessException | IOException e1) { e1.printStackTrace(); }
}
in my JPanel class. Anyhow the result is this:Gif1
if I remove repaint() method the result is: Gif2
The image to drag is a BufferedImage Object
My question is: How I can do the drag without flickering or snake effects?
This is not how the paint mechanism works in Java. To explain your problem, you need to understand these points:
When you want to paint something permanently, you should override the paintComponent() method of your component to draw the stuff you want. So for your purpose, you should store the new image location in the panel, and call drawImage(image, newLocation.x, newLocation.y) in the panel's paintComponent() method.
When you try to getGraphics() and paint something to it, the stuff you paint will appear immediately, but it will be cleared at next round paint occurs. It is because every round of paint will clear all old painted stuff and ask all component to paint new stuff again.
When you trigger repaint(), you request for a new round of paint, this request is scheduled and coalesce which does not happen immediately, but is guaranteed to happen sometime later. You should always call repaint() instead of calling getGraphics().doSomething(), not to mentioned that getGraphics() may return null if the component is not showing.
This explains why with repaint(), the image will flicker, because your call to getGraphics().drawImage() force it to draw something immediately, then you triggered a new round of paint(), which cleared old content, but you does not provide something new to draw. This caused the image shown then image cleared effect.
But if without repaint(), you forced image at new position to draw immediately, but old content never cleared, caused the snake effect.
Related
private void moveSquare(int x, int y) {
int OFFSET = 1;
if ((squareX!=x) || (squareY!=y)) {
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
squareX=x;
squareY=y;
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
}
}
In the above code (complete code can be found in the Demo App of the "Performing Custom Painting" Java Tutorial), the first repaint method should paint a square at the position of the previous square, and the second repaint should paint another square at position of the new square. But this is actually not happening. Instead, the previous square disappears and the new one is painted.
How does the new square get painted while the previous one disappears?
The docs you linked to answer your question, at least in general:
although we have invoked repaint twice in a row in the same event handler, Swing is smart enough to take that information and repaint those sections of the screen all in one single paint operation.
When you call repaint, you are not actually painting anything yet, but requesting a repaint at some time in the future.
While the repaint JavaDoc doesn't go into any detail, it includes a link to "Painting in AWT and Swing", which includes in the "Paint Processing" section two cases, the second of which applies here:
(B) [When the] paint request originates from a call to repaint() on an extension of javax.swing.JComponent:
JComponent.repaint() registers an asynchronous repaint request to the component's RepaintManager, which uses invokeLater() to queue a Runnable to later process the request on the event dispatching thread.
And later in that section:
NOTE: if multiple calls to repaint() occur on a component or any of its Swing ancestors before the repaint request is processed, those multiple requests may be collapsed into a single call back to paintImmediately() [...]
By the time your event handler returns, some portion(s) of the JPanel will have been marked for repainting, possibly all of it. These are called "dirty regions". Swing (eventually) repaints all the dirty regions at once, and only once. This painting happens after your event handler has returned --- meaning after the JPanel's appearance has been changed --- so the colored square appears in its new location, without any "leftovers" in its old location.
In short, don't think of repaint as "repaint this area right now", but "add this area to your list of stuff-to-paint-sometime-later".
After you call repaint() it do not repaint the component instantly. But it add the request to paint the component again in the event queue in EDT.
What happens in each code line is expained below..
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
Mark the area bounded by the square (squareX,squareY,squareW+OFFSET,squareH+OFFSET) is going to be repaint. But it do not get repainted until RepaintManager do so..
squareX=x;
squareY=y;
Change the value of squareX and squareY. But it do not change the earlier marked region to be repainted. Now also, the region to be repainted is previous values.
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
Mark the area bounded by the square (squareX,squareY,squareW+OFFSET,squareH+OFFSET) is going to be repaint. Now there are two parts that RepaintManager has to repaint. Previous square and new square. But it do not get repainted until RepaintManager do so..
Finally when the time comes, RepaintManager paints the component.
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString("This is my custom Panel!",10,20);
g.setColor(Color.RED);
g.fillRect(squareX,squareY,squareW,squareH);
g.setColor(Color.BLACK);
g.drawRect(squareX,squareY,squareW,squareH);
}
Now the component is painting only 2 areas. (Previous square and the new square) But the red square will be drawn only within the new square. In the old square there is nothing to draw. So previously drawn things will be erased..
Actually though there are 2 method calls for repaint(), paintComponents() will be called only once. The total area to be repainted is handled by RepaintManager and paintComponents() is called only once..
Oracle docs gives good explanation:
moveSquare method invokes the repaint method not once, but twice. The first invocation tells Swing to repaint the area of the component where the square previously was (the inherited behavior uses the UI Delegate to fill that area with the current background color.) The second invocation paints the area of the component where the square currently is.
I don't want to be too specific because this could have many applications but if required I can post some code. For simplicity let's say I have an RCP view with a Canvas on and I want to draw a circle on that canvas in the centre. Not with every re-draw, I just want it to start in the centre.
I have tried to do this in the constructor of my extended canvas but the getSize() method doesn't yet return the correct size. I have tried to pack() and layout() the parent composite of the View and then get the canvas size, still nothing.
So is there somewhere to catch the initial size of the View and components being set?
Listen to the SWT.Resize event. You will get a resize when the control is created.
I'm not sure what you mean "not with every redraw," but usually, to paint items (and have them continue to be painted, you have to actually "paint" in a control's PaintListener's paintControl() method. This may be what you are looking for:
canvas.addPaintListener(new PaintListener() {
#Override
public void paintControl (PaintEvent event) {
Rectangle clientRect = canvas.getClientArea();
event.gc.drawOval(0, 0, clientRect.width - 1, clientRect.height - 1);
});
So you can get the "size" via the control's (canvas's) client-area, and then paint accordingly. You'd need to change the params in the drawOval method to make your circle the appropriate size.
That's how to "paint" and maintain the drawing. Otherwise, it will disappear.
My question is that i need to make a GUI that keeps updating becuse i get values that can change from a DB and i got some problems in the graphical area.
well im using Drawline and Drawstring from Graphics2D that print values that are found on the database, this strings and lines move and change value, so i need to call repaint(); with a timer to make them apper in the jpanel, the problem is that repaint(); is not removing the old painting in the background before painting, but when i resize all updates perfecly.
i know a way to clear but the background color goes away too so,
There is a way to update the jpanel removing old paintings and keep the deafult background color?
Not updated
After changing a coordenate and a label text to "AXIS Y" (repaint called automatically from a timer)
Thanks.
From the looks of your image, it looks like you're just forgetting to call super.paintComponent in the paintComponent method. What this does is repaint the background for you, so aren't left with the previous paint artifacts.
#Override
protected voud paintComponent(Graphics g) {
super.paintComponent(g);
}
Note: For future reference, though the images gave us a good picture, it always best to post a Minimal, Complete, and Verifiable example along with those images, so we don't have to make guesses (educated or not)
I would know if my implementation is correct for double buffered image.. because i note that tremble the borders of my image that i move in the screen ... It is normal??
public void paintComponent(Graphics g) {
Image bufferimage= createImage(180,180);
Graphics dbg= bufferimage.getGraphics();
//clean the screen
dbg.setColor(new Color(100,100,100));
dbg.fillRect(0,0,getWidth(),getHeight());
if (game_is_running) {
// draw various type of object with drawImage
for(int i=0; list[i]!=null; i++) {
list[i].draw(dbg);
}
target.draw(dbg);
I.draw(dbg);
}
//finally draw the image linked to graphics
g.drawImage(bufferimage,0,0,this);
}
Move the creation of the bufferimage out of the paintComponent() method. You don't need to create this every time that method is called. You are drawing the whole surface anyway.
When you are done with the Graphics retrieved from bufferImage (i.e. dbg variable in your case) you are supposed to call dispose() on it.
Finally you might be able to get away without the second image if you ensure that your component and the components that contain it, have the property doubleBufferred set to true.
All the paintComponent() method should do is draw the image.
The "if game is running" code does not belong in the paintComponent() method. The idea is to have a Timer or something that changes the state of your game and does the custom drawing on your image. Then when Swing invokes the paintComponent() method you simple paint the image in its current state.
Take a look at the DrawOnImage example from Custom Painting Approaches. The code adds rectangles to the image by using the mouse. Then whenever the component is repainted the image is painted.
The ideas is to create/modify the image once and then repaint the image. In a game it may turn out that every time you modify the image you also paint it, but the code should not be part of the paintComponent() method.
I have a component on which I'm drawing a BufferedImage on all the surface.
I would like to draw something more over it, following the mouse when it passes over the area.
To do it, I'm adding a MouseMotionListener on the component and implement mouseMove method. Inside mouseMoved method I'm calling repaint() at the end of the drawing of the drawing of the cursor image. I would like to know if there is a better way to do it, cause the image following the cursor is really small, and I'm repainting every thing each time.
Add a JLabel containing an Icon to the panel with the buffered image.
Then when you move the mouse you just change the location of the label. Swing will repaint the last location so the buffered image shows through, then it will repaint the label at the new location. So let Swing manage the repaint.
Since you know the coordinate of your mouse and the small image you gonna paint over your background, you can optimize like this [pseudo-code]:
void mouseMoved(event) {
lastCoordinates = currentCoordinates;
currentCoordinates = event.coordinates;
image.repaint(lastCoordinates.x, lastCoordinates.y, smallImage.width, smallImage.height);
image.repaint(currentCoordinates.x, currentCoordinates.y, smallImage.width, smallImage.height);
}
that way you only repaint the two regions you actually care about instead of the whole background.
Also, reading the javadoc it seems the code above my actually trigger 2 separate calls to painting stuff, which would be inefficient. You may want to try to pass in a 10 milliseconds value or so to make sure the 2 paints execute together.
Check out javadoc for repaint() that takes 4 and 5 arguments:
4-argument version
5-argument version