I have a simple JPanel that I'm using to display an image. I want some functionality where I'm able to pan the image by dragging it. I have something like this (note the code I have compiles and runs properly; the code below is heavily truncated to just get an idea of what I'm doing):
public class Data2DPanel extends JPanel {
public Data2DPanel(Data2D data) {
// Set image
this.image = Data2D.data2DToBufferedImage(data);
// Set mouse listener
Data2DMouseAdapter data2DMouseAdapter = new Data2DMouseAdapter();
this.addMouseListener(data2DMouseAdapter);
this.addMouseMotionListener(data2DMouseAdapter);
}
private class Data2DMouseAdapter extends MouseAdapter {
#Override
public void mouseDragged(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
switch (actionState) {
case PAN:
xOffset = xOffsetInit + e.getX()-xPosInit;
yOffset = yOffsetInit + e.getY()-yPosInit;
paintComponent(getGraphics());
break;
}
}
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
// Paint image
g2.drawImage(image,x,y,width,height,this);
}
}
}
The problem is that there is a lot of flickering. I've checked stackoverflow/google and it seems a lot of the flickering problems are because people override the paint method instead of the paintComponent method. I've also checked isDoubleBuffered and it returns true. Intuitively, I feel maybe the mouseDragged method is updating too much for paintComponent() to keep up, but I figured double buffering should still prevent flickering from occuring. My question is if there's something inherently wrong with this approach and if there's a standard or elegant solution to this.
paintComponent(getGraphics()); should be repaint(). Compounded problems.
You never want to make a call to getGraphics() for custom painting. The only Graphics object used to paint should be the one provided in the paint method.
You should never call paintXxx to try and "force" a repaint of the component. You should call repaint() and allow the RepaintManager to handle all the update and paint cycle
Related
I figured out a solution minutes after posting, but due to low reputation I couldn't delete the post
For the fun of it I decided to start working on something which might turn into a game at some point.
I'm trying to draw some circles and move them in a given direction currently. This causes flickering. It's very likely that I oversee something very basic but I can't figure out why it doesn't render smoothly.
My board class looks something like (removed what I deemed unnecessary):
public class Board extends Canvas implements Runnable {
public static void main(String[] args) {
Board board = new Board();
board.setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
JFrame frame = new JFrame("Circles");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(board);
frame.pack();
frame.setVisible(true);
board.start();
}
#Override
public void run() {
while (running) {
process();
repaint();
try {
Thread.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
The paint method:
public void paint(Graphics g1) {
super.paint(g1);
Graphics2D g = (Graphics2D) g1;
for (Ball ball : Ball.BALLS) {
g.drawOval((int) ball.getLocation().getX(), (int) ball.getLocation().getY(), ball.getRadius(), ball.getRadius());
}
}
My process method:
private void process() {
if (utilities.randInt(1, 100) < 10 && Ball.getBallCount() < 40) {
Ball.spawnNew(this);
}
for (Ball ball : Ball.BALLS) {
ball.move(this);
}
Ball.BALLS.removeAll(Ball.TO_REMOVE);
Ball.TO_REMOVE.clear();
}
The move method basically increments the x-value of the ball by a given value each time its called (moving it right).
Like I said, I'm unsure why it flickers so if you have any pointers please do tell.
Thanks!
This sounds like a case where you need to perform double-buffering, so that one copy of your canvas can remain shown while you are updating the other.
You're using AWT here, and I don't know how to implement double-buffering manually with AWT. However, if you're willing to use Swing here you can take advantage of automatic double-buffering. See the question about How to make canvas with Swing? as well as Oracle Technology Network's article on Painting in AWT and Swing.
The basic idea would be:
extend javax.swing.JPanel instead of Canvas (which means when you override paint(Graphics) you're now overriding it from javax.swing.JComponent instead of java.awt.Component)
create a constructor with super(true) to enable double-buffering.
Edit: Also, as iccthedral points out, you're better off overriding paintComponent(Graphics) and including a call to super.paintComponent(Graphics). See Difference between paint, paintComponent and paintComponents in Swing.
You need double buffering. To do this you need to create a BufferedImage and get the Graphics from it. Paint everything to the image, render the image on to the screen, then finally fill the image with a the background color or image to reset it.
Sample:
//one time instantiation
BufferedImage b = new BufferedImage(width, height, mode);
In paint(Graphics g):
Graphics buffer = b.getGraphics();
//render all of the stuff on to buffer
Graphics2D g = (Graphics2D) buffer;
for (Ball ball : Ball.BALLS) {
g.drawOval((int) ball.getLocation().getX(), (int) ball.getLocation().getY(), ball.getRadius(), ball.getRadius());
}
g1.drawImage(b, 0, 0, width, height, null);
g.setColor(Color.BLACK);
//reset the image
g.drawRect(0, 0, width, height);
g.dispose();
I have the following code that draws an invisible window on the entire screen:
Window w=new Window(null)
{
private int x = 200; private int y=200;
private int dx = 2; private int dy = 2;
private final int CIRCLE_DIAMETER = 400;
#Override
public void paint(Graphics g)
{
g.setColor(Color.ORANGE);
g.fillOval(x, y, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
#Override
public void update(Graphics g)
{
if(x<=0)
dx*=-1;
if(y<=0)
dy*=-1;
if(x+CIRCLE_DIAMETER>=this.getWidth())
dx*=-1;
if(y+CIRCLE_DIAMETER>=this.getHeight())
dy*=-1;
x+=dx;
y+=dy;
this.paint(g);
}
};
w.setAlwaysOnTop(true);
w.setBounds(w.getGraphicsConfiguration().getBounds());
w.setBackground(new Color(0, true));
w.setVisible(true);
//Lazy way of making calls to paint for testing
while(true){
w.repaint();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
This draws an orange circle on the screen at the coordinates x and y. When I call repaint in my infinite loop, paint gets called and the x and y get updated, but the circle never gets drawn in another position. If I print the values of x and y to every call of paint they are getting updated properly, so I do not know why it isn't getting drawn. Does anyone know what I am doing wrong here?
Thanks for any suggestions!
I am new here so I may be wrong.
I think that your problem is something to do with how you are using a Window object and not a JPanel. So change your Window object to a JPanel. You should probably close that up with a JFrame in order to complete the final window.
You should be using a JPanel I think so that the methods that you can use to perform the drawing of the ball to move are implemented properly.
Instead of overriding the paint() method you need to override the paintComponent() method.
Following the cycle of drawing your objects.
Like so...
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.ORANGE);
g.fillOval(x, y, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
The super.paintComponent() should empty the original image out of the JPanel and you should then be able to draw your updated image.
These may help you as well(I haven't really looked at them properly):
Java ball moving
http://docs.oracle.com/javase/tutorial/uiswing/painting/
Java Bouncing Ball
Sorry if I missed something. (I haven't tested your code)
I'm looking to move a jpanel inside of a JFrame, and it won't seem to budge. I can set it's location in the paint() method, but it won't update in repaint. Please help! Here is my code:
public void paint(Graphics g) {
g.drawImage(playerImg, x, 50, null);
this.setLocation(x, 50);
}
public void update() {
this.repaint();
}
public void keyPressed(KeyEvent key) {
if(key.getKeyCode() == KeyEvent.VK_UP) {
x = x + 50;
System.out.println("e");
update();
}
}
"I can set it's location in the paint() method" - Don't, seriously, you should never modify the state of any component within any paint method, in fact, you've broken the paint chain by not calling super.paint, which is going to cause you no end of other problems.
Instead, set the parent containers layout manager to null, you will now find that the component disappears. This is because the layout manager is responsible for setting the size and position of the component, which you will have to take over control of.
Instead of overriding paint you should be overriding paintComponent and calling super.paintComponent. Take a look at Performing Custom Painting for more details
I am a beginner when it comes to making Java applets, and for my first applet, I drew a smiley face using paint(). Now, I want to make the smiley face blink. I have managed to get my timers and everything set up, but I need to use the start() method to get the timers going, and it seems that by including other methods, the paint method does not invoke itself. Because of this, I am assuming that I need to invoke paint() from start(), but the problem is I do not know what I am supposed to initialize the Graphics variable to in order to get paint() to actually work.
SSCCE
import java.awt.*;
import javax.swing.*;
import java.applet.Applet;
import java.awt.event.*;
public class Project2_15 extends Applet
{
public void paint(Graphics g)
{
setBackground(Color.lightGray);
}
// This handles the starting of timer execution.
public void start()
{
Graphics g; // What do I initialize this to?
paint(g);
}
// Timer Stuff
ActionListener blinkShut;
public Project2_15(final Graphics g) {
this.blinkShut = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
g.setColor(Color.black);
}
};
}
}
Here is the code with some corrections:
import java.awt.*;
import javax.swing.*;
import java.applet.Applet;
import java.awt.event.*;
public class Project2_15 extends Applet
{
public boolean wink = false;
Timer timer;
public void paint(Graphics g)
{
super.paint(g);
// Graphics g; // What do I initialize this to? ALREADY INITIALIZED
//paint(g);
if (wink) {
g.drawLine(1,1,100,100);
} else {
g.drawOval(1,1,100,100);
}
}
// This handles the starting of timer execution. NO IT DOES NOT!
// public void start()
#Override
public void init()
{
setBackground(Color.lightGray);
timer = new Timer(250,blinkShut);
}
#Override
public void start() {
timer.start();
}
#Override
public void stop() {
timer.stop();
}
// Timer Stuff
ActionListener blinkShut = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
wink = !wink;
repaint();
}
};
}
See Performing Custom Painting. It would be the same basic process with applet or frame.
Add a container (Panel/JPanel) to the top-level container.
Override the paint(..) AWT or paintComponent(..) Swing method.
Call super.. as the first statement.
Do the custom painting to the supplied Graphics instance.
Animation can be achieve using a Swing based Timer.
Of course I would tend to replace steps 1) to 4) with painting to a BufferedImage displayed in a JLabel/ImageIcon.
You need your timer to change the state of the applet, suggest that it be repainted, and then have your applet's paint method to react to the state. Some suggestions:
Use a Swing Timer for your timer
Give your applet a non-static boolean field called blink.
In the Timer's actionPerformed method, change the boolean field of the applet, blink, to its opposite state: blink = !blink;
Then call repaint(). This will tell the JVM to possibly repaint the applet.
In your paint(...) method use the state of the blink variable in an if block, and if true paint an eye, if false paint a closed eye.
You're better off using a Swing applet or JApplet.
If you're using a JApplet, then you'll do your painting in a JPanel's paintComponent(...) method, not in the paint method.
Either way, be sure to call the super method as the first method call in your painting method, either super.paint(g) if in the Applet's paint method or super.paintComponent(g) if in a JPanel's paintComponent method. This allows your GUI to erase previous painting.
Edit
Regarding your code:
public void start()
{
Graphics g; // What do I initialize this to?
paint(g);
}
and:
public Project2_15(final Graphics g) {
this.blinkShut = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
g.setColor(Color.black);
}
};
}
Please throw this code out as you almost never paint this way or call paint directly. Please read or re-read my recommendations above.
Edit 2
Regarding your comments:
So I can't just create a separate timer and put the code in their?
I never said this. Feel free to use a separate timer, and put in decent code inside of it. You of course will have to discard your current code since you do not want to manipulate the Graphics object directly as you're trying to do.
In addition to his eyes blinking, I was also hoping to have his tongue go in and out using a separate timer.
Then go for it!
I have a JComponent with a listener on it. On the JComponent, I draw a big image and the mouse listener adds small images where clicks occur (one big map on which I add some dots).
How can I programatically draw something outside the paintComponent method?
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(img1, 0, 0, this);
g2.finalize();
}
private MouseListener listener;
public void initListener() {
myCanvas = this;
listener = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
myCanvas.getGraphics().drawImage(img,e.getX(),e.getY(), myCanvas);
}
};
addMouseListener(listener);
}
My problem is with this:
public void drawDot(int x, int y){
myCanvas.getGraphics().drawImage(img, x, y, myCanvas);
}
It doesn't do anything. I have tried repaint().
You can't do this. All drawing occurs in the paintComponent() method. What you should do is build a model that represents what you want to draw, and modify the model in your mouse listener. Then call repaint() to ask that this component be redrawn when the model is modified. Inside your paint() method render the full paint from the model. For example:
List<Point> pointsToDrawSmallerImage = new ArrayList<Point>();
...
listener = new MouseAdapter() {
public void mouseClicked(MouseEvent evt ) {
pointsToDrawSmallerImage.add( evt.getPoint() );
repaint();
}
}
...
public void paintComponent(Graphics g) {
g.clear(); // clear the canvas
for( Point p : pointsToDrawSmallerImage ) {
g.drawImage(img, p.x, p.y, myCanvas);
}
}
You have to manage the drawing inside the paintComponent method. Java Graphics is not stateful, you have to take care of what you actually need to draw whatever you want inside the method. Every time the paint method is called, everything must be drawn again, there is nothing that "stays" on the canvas while adding other components
This means that you should store a list of elements that the paint method will take care to draw, eg. ArrayList<Point> points, then in paint method you should iterate them:
for (Point p : points)
draw the point
so that you just add the point to the list with the listener and call repaint.
You can find guidelines for Swing/AWT drawing here..
A particual API has the behavior you would like to have though, it is called Cocos2D and it has a port for Android/Java that you can find here.
that is not how draw works, the draw method paints everything which is in the method itself on every repaint,
that means if you call a method to draw something once, it will only be drawed for one repaint cycle and that's it.
if you want something t be drawn on click you have to add it on on click to a collection and draw the whole collection in every paint cycle, so it will stay permanently.