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.
Related
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
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'm trying to draw inside my JPanel but everytime I click, the background of my JPanel disappears. It draws a line where the mouse is. I think it has something to do with the 2D graphics
Can someone help?
public Brush() {
addMouseListener(this);
addMouseMotionListener(this);
setBackground(Color.white);
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2;
// super.paintComponent(g);
g2 = (Graphics2D) g;
g2.setColor(brushColor);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(8, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
//Ellipse2D.Double circle = new Ellipse2D.Double(p1.x,p1.y,20,20);
g2.fillOval(p1.x,p1.y,20,20);
}
#Override
public void mousePressed(MouseEvent e) {
dragging = true;
p1 = e.getPoint();
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
dragging = false;
p1 = e.getPoint();
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
if (dragging) {
p1 = e.getPoint();
repaint();
}
}
Always call the super.paintComponent(g) method inside of your override.
You're drawing wrong then. If you want to draw a bunch of ovals, then either
create a collection of them and draw them with a for loop in paintComponent, or
draw them in a BufferedImage which is then drawn in your paintComponent method.
If I want to draw a curve with the mouse, I usually create an ArrayList<Point> and draw lines between contiguous points, either in paintComponent or in a BufferedImage.
Again, your code is written to draw only one point (oval actually) within paintComponent. If coded correctly, this is all it will do.
I suggest, the easiest thing to do is:
Give you class an ArrayList<Point>
Add points when mouse is pressed and call repaint
In paintComponent, call the super method, and then use a for loop to iterate through the ArrayList.
Start the loop at the Point at item 1, not 0, and then draw a line between the previous Point and the current point.
To get fancier, you may wish to have an ArrayList<ArrayList<Point>> where you start a new ArrayList<Point> with each press of the mouse, finish it with each release and add it to the overall collection. This will allow several lines to be drawn.
Why not give this a go on your own first?
Im trying to get the point of the cursor in the paint part and simply draw an oval. No luck though!
public void paint(Graphics g){
Point ComponentPoint = PaintPanel.getLocationOnScreen();
Point CursorPoint= MouseInfo.getPointerInfo().getLocation(); //gets cursor point
int ComPX = ComponentPoint.x;
int ComPY = ComponentPoint.y;
int CurPX = CursorPoint.x;
int CurPY = CursorPoint.y;
int FinalX = CurPX - ComPX;
int FinalY = CurPY - ComPY;
g.drawOval(FinalX, FinalY, 20, 20);
}
private void PaintPanelMouseDragged(java.awt.event.MouseEvent evt) {
//when mouse is moved over paintpanel
//PaintPanel.repaint();
not working
}
This is it without paint method, the image:
http://i.stack.imgur.com/VOyhr.png
You can't add code in the paint method like that. YOu would not reference the MouseInfo class in the paint method since you have no control over when the paint() method is invoked. You should be using a MouseListener and MouseMotionListner to do custom painting. Also, custom painting should not be done in the paint method.
See Custom Painting Approaches for two solutions.
how to turn several graphics objects in to one?
(this part of code should generate tetris figure, where generate() create a figure)
public void paint(Graphics g){
Figure f = generate();
int length = f.getX()[0].length;
for(int j =0; j<f.getX().length;j++){
int xr=xs+10;
ys = 0;
for(int i=0;i<length;i++){
if (f.getX()[j][i] == 1){
int yr = ys+10;
Rectangle p = new Rectangle(xs,ys,xr,yr);
g.setColor(f.getY());
g.drawRect(p.x, p.y, p.width, p.height);
g.fillRect(p.x, p.y, p.width, p.height);
//g.translate(xs+40, ys+40);
}
ys+=10;
}
xs+=10;
}
xs=0;
ys=0;
//g.setColor(Color.white);
//g.drawRect(45, 95, 55, 105);
}
Well, I think you are starting with Java 2D, since your code has some problems.
First of all, you always need to call the paint version of the super class. This should be done because the component needs to have a chance to render itself properly. Take a look.
#Override
public void paint( Graphics g ) {
// you MUST do this
super.paint(g);
// continue here...
}
If you are dealing with a JFrame you will override the paint method. If you are working with some JComponent child, like JPanel, you need to override the paintComponent method, which has the same signature of paint, but it is protected, not public. You can override paint too, but in these cases (JComponent and its children), paint is a method that delegates the paint work to three methods (paintComponent, paintBorder, and paintChildren), so the best option is to override paintComponent.
Another detail. The best way to work with graphics is to create a new graphics context based in the current one and dispose of it after using it. Take a look:
#Override
public void paint( Graphics g ) {
// you MUST do this
super.paint(g);
Graphics newG = g.create();
// or Graphics2D newG2d = (Graphics2D) g.create();
// do your work here...
newG.dispose(); // disposes the graphics context
}
The graphics context that is created using the create method is a copy of the current graphics context (with the same states), but changing it does not affect the original one, so doing this, you will not mess with the state of the original graphics context.
To finish, I think that you need to have a draw method in your figure that receives the graphics context. So, the Figure instance will be responsible to draw itself. Something like:
public class Figure {
// figure's members...
public void drawMe( Graphics2D g2d ) {
// do the draw work here...
}
}
public class MyFrame extends JFrame {
#Override
public void paint( Graphics g ) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g.create();
// let's suppose that figureList is a List<Figure> or a Figure[]
for ( Figure f : figureList ) {
f.drawMe( g2d );
}
g2d.dispose();
}
}
Of course, you can create a new graphics context for each Figure if its draw method changes the graphics context too "deeply", like doing translations and rotations. You just need to dispose the new ones after using them.
I assume you are trying to put multiple components inside of an enclosing component so that you can move/manipulate them together.
One suggestion would be to add each of your objects to a panel object, like JPanel.
However it is somewhat unclear what you are trying to achieve exactly.