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?
Related
I am trying to make a program with a simple grid of rectangles where, if the user clicks on a rectangle, it is filled black. The problem I am having is that my mouseClicked method cannot access my paintComponent method, so I get an error.
Here is relevant code:
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
super.paintComponent(g2);
g2.setColor(Color.BLACK);
for(Rectangle2D rect : squares) {
g2.draw(rect);
}
}
public void mouseClicked(MouseEvent e) {
if((e.getX()>RECT_WIDTH && e.getX()<RECT_WIDTH+(game.getSize()-1)*BOX_DIM) && (e.getY()>RECT_HEIGHT && e.getY()<RECT_HEIGHT+(game.getSize()-1)*BOX_DIM)) {
Point2D point = new Point2D.Double(e.getX(), e.getY());
Rectangle2D rect = findRect(point);
g.setColor(Color.BLACK);
g.fill(rect);
repaint();
}
}
public Rectangle2D findRect(Point2D p) {
for(Rectangle2D rect : squares) {
if(rect.contains(p)) {
return rect;
}
}
return null;
}
"squares" is an arraylist of rectangle2Ds. The error is in the mouseClicked method on 'g' because eclipse cannot find g. Thanks for any help!
In your List object you need to store an object that contains two pieces of information:
The Rectangle
the Color of the Rectangle
When you click on the Rectangle you iterate through the List to find the Rectangle that was clicked and then you update the Color property of that Rectangle and invoke repaint().
You will also need to change the painting code to set the Color of the Rectangle before you invoke the draw() method.
Check out the DrawOnComponent example found in Custom Painting Approaches. It shows how to create the custom object to store two properties and how to paint this object in the paintComponent() method.
I am trying to do a little program on Eclipse. The program goes like this: when I click for the 1st time on thr Panel on the frame, a line has to be drawn regarding the Y position of my mouse listener.The line takes all the width of the panel. On the 2nd click, another line has to be drawn, again regarding the Y position of where I clicked. After, I'll put a little circle between the 2 lines and make a little animation with it.
But now, I have a problem. When I click on the panel, a line is drawn, but if i click another time, the first line disappears and the 2nd line takes it place...
This is the code of the painComponent and my mousr listener. What is wrong with it ?
public Lines() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
posY=e.getY();
posX=e.getX();
nbClic=e.getClickCount();
repaint();
}
});
setBackground(Color.black);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.blue);
if(nbClic>=1){
line1=new Line2D.Double(0, posY, getWidth(), posY);
g2d.draw(line1);
repaint();
}
if(nbClic>=2){
g2d.setColor(Color.YELLOW);
line2=new Line2D.Double(0, posY, getWidth(), posY);
g2d.draw(line2);
}
repaint();
}
Painting is an event that draws the entire component. You can't depend on past events because they are erased each time a repaint happens.
You would need to keep something like a List and each time you create a new line, you add it to the List.
List<Integer> yClicks = new ArrayList<>();
... {
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
yClicks.add(e.getY());
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g.create();
for(int y : yClicks) {
g2d.draw(new Line2D.Double(0, y, getWidth(), y));
}
g2d.dispose();
}
Also:
Never call repaint inside paintComponent! This will cause an endless cycle of repaints.
paintComponent is a protected method and should remain so unless there is a compelling reason to make it public.
Be careful changing the state of the Graphics object passed in to paintComponent because it is used elsewhere. Usually we create a local copy which is disposed when we are done.
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've got a problem in Java using a "canvas" class I created, which is an extended JPanel, to draw an animated ring chart. This chart is using a MouseListener to fetch click events.
The problem is that the mouse position does not seem to be accurate, meaning it does not seem to be relative to the "canvas" but instead relative to the window (in the left, upper corner I got about 30px for y coord).
This is my code:
I created a class, that extends JPanel and does have a BufferedImage as member.
public class Canvas extends JPanel {
public BufferedImage buf;
private RingChart _parent;
public Canvas(int width, int height, RingChart parent){
buf = new BufferedImage(width, height, 1);
...
In the paint component method I just draw the buffered image, so I am able to paint on the canvas from 'outside' by painting on the buffered image, which is public.
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.drawImage(buf, null, 0, 0);
}
Now there's a class RingChart which contains a "canvas":
public class RingChart extends JFrame{
public Canvas c;
...
And I create a Graphics2D from the bufferedImage in the canvas class. This g2d is used for painting:
public RingChart(){
c = new Canvas(1500,980,this);
add(c);
setSize(1500, 1000);
setTitle("Hans");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
g2d = (Graphics2D)c.buf.createGraphics();
...
What I now was trying to achieve, was a mouse listener that listened to mouse events happening on the canvas. So when the user clicks on the canvas I could retrieve the position he clicked on, upon the canvas, through the event variable.
So I created a mouse listener:
class MouseHandler implements MouseListener {
#Override
public void mouseClicked(MouseEvent e){
RingChart r = ((Canvas)e.getSource()).getParent();
r.mouseClick(e);
}
...
...and added this mouse listener to the canvas of the RingChart class (myChart is an instance of RingChart and c is the canvas it contains):
...
MouseHandler mouse = new MouseHandler();
myChart.c.addMouseListener(mouse);
...
But as I mentioned above, the mouse position, that's returned when the click event is called, does not seem to be accurate. I think the mistake must be somehow in the way I created that mouseListener or maybe assigned it to the wrong element or something like that. But I've tried quite a couple of things and it didn't change. Can maybe someone tell me, what I've done wrong?
UPDATE:
The code of the function "mouseClick" that is a member of RingChart and is called in the mouse listener:
public void mouseClick(MouseEvent evt){
//evt = SwingUtilities.convertMouseEvent(this, evt, c);
if(evt.getButton() == MouseEvent.BUTTON1 && animation == null){
for(Element e : elements){
if(e.getShape() != null && e.getShape().contains(evt.getPoint())){
//do some stuff
}
}
}
}
Again, the hierarchy of my classes:
RingChart --contains a--> Canvas --got a--> MouseListener.
The shapes in this function are shapes that have been painted on the canvas c. Now I want to check, if the user has clicked on one of them. So as I thought, the shapes should be in canvas-coordinates and the event position should be in canvas-coordinates and everything should fit together. But it doesn't.
Now user MadProgrammer told me, to use the ConvertMouseEvent function. But I currently don't see which exact way I should use this sensibly.
UPDATE:
I found a solution: All I had to do is adding the canvas not directly to the JFrame but to the ContentPane of the JFrame instead:
So instead:
public RingChart(){
c = new Canvas(1500,980,this);
add(c);
...
I do:
public RingChart(){
c = new Canvas(1500,980,this);
getContentPane().add(c);
...
Then I give the MouseListener to the ContentPane.
getContentPane().addMouseListener(new MouseHandler());
getContentPane().addMouseMotionListener(new MouseMoveHandler());
I don't know, if this is an elegant solution, but it works.
The mouse event is automatically converted to be relative to the component that it occurred in that is, point 0x0 is always the top left corner of the component.
By using RingChart r = ((Canvas)e.getSource()).getParent(), you've effectively changed the reference, which now means the location is no longer valid.
You need to convert the location so that its coordinates are in the context of the parent component. Take a look at SwingUtilities.convertMouseEvent(Component, MouseEvent, Component)
UPDATE with PICTURES
Lets take this example...
The blue box has a relative position of 50px x 50px to the red box. If you click in the blue box, lets say at 25x25, the mouse coordinates will be relative to the blue box (0x0 will be the top left of the blue box).
If you then pass this event to the red box and try and use the coordinates from it, you will find that the coordinates will now be half way between the top left of the red box and the blue box, because the coordinates are context sensitive.
In order to get it to work, you need to translate the mouse events location from the blue box to the red box, which would make it 75x75
Now, I don't know what you're doing when you pass the mouse event to the RingChart so I'm only guessing that this is the issue you're facing.
UPDATED with Click Code
Okay, lets say, you have a Canvas at 100x100. You click on that Canvas at 50x50. You then pass that value back up the chain.
public void mouseClick(MouseEvent evt){
//evt = SwingUtilities.convertMouseEvent(this, evt, c);
if(evt.getButton() == MouseEvent.BUTTON1 && animation == null){
for(Element e : elements){
// Here, we are asking the shape if it contains the point 50x50...
// Not 150x150 which would be the relative position of the click
// in the context to the RingChart, which is where all your objects
// are laid out.
// So even the original Canvas you clicked on will return
// false because it's position + size (100x100x width x height)
// does not contain the specified point of 50x50...
if(e.getShape() != null && e.getShape().contains(evt.getPoint())){
//do some stuff
}
}
}
}
UPDATED
I think you have your references around the wrong way...
public static MouseEvent convertMouseEvent(Component source,
MouseEvent sourceEvent,
Component destination)
I think it should read something like
evt = SwingUtilities.convertMouseEvent(evt.getComponent(), evt, this);
UPDATE with Code Example
Okay, so, I put this little example together...
public class TestMouseClickPoint extends JFrame {
private ContentPane content;
public TestMouseClickPoint() throws HeadlessException {
setSize(600, 600);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setLayout(new BorderLayout());
content = new ContentPane();
add(content);
}
protected void updateClickPoint(MouseEvent evt) {
content.updateClickPoint(evt);
}
protected class ContentPane extends JPanel {
private Point relativePoint;
private Point absolutePoint;
public ContentPane() {
setPreferredSize(new Dimension(600, 600));
setLayout(null); // For testing purpose only...
MousePane mousePane = new MousePane();
mousePane.setBounds(100, 100, 400, 400);
add(mousePane);
}
protected void updateClickPoint(MouseEvent evt) {
absolutePoint = new Point(evt.getPoint());
evt = SwingUtilities.convertMouseEvent(evt.getComponent(), evt, this);
relativePoint = new Point(evt.getPoint());
System.out.println(absolutePoint);
System.out.println(relativePoint);
repaint();
}
protected void paintCross(Graphics2D g2d, Point p) {
g2d.drawLine(p.x - 5, p.y - 5, p.x + 5, p.y + 5);
g2d.drawLine(p.x - 5, p.y + 5, p.x + 5, p.y - 5);
}
/*
* This is not recommended, but I want to paint ontop of everything...
*/
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
if (relativePoint != null) {
g2d.setColor(Color.BLACK);
paintCross(g2d, relativePoint);
}
if (absolutePoint != null) {
g2d.setColor(Color.RED);
paintCross(g2d, absolutePoint);
}
}
}
protected class MousePane extends JPanel {
private Point clickPoint;
public MousePane() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
clickPoint = e.getPoint();
TestMouseClickPoint.this.updateClickPoint(e);
repaint();
}
});
setBorder(new LineBorder(Color.RED));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.BLUE);
if (clickPoint != null) {
g2d.drawLine(clickPoint.x, clickPoint.y - 5, clickPoint.x, clickPoint.y + 5);
g2d.drawLine(clickPoint.x - 5, clickPoint.y, clickPoint.x + 5, clickPoint.y);
}
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
new TestMouseClickPoint().setVisible(true);
}
}
Basically, it will paint three points. The point that the mouse was clicked (relative to the source of the event), the unconverted point in the parent container and the converted point with the parent container.
The next thing you need to do is determine the mouse location is actually been converted, failing that. I'd probably need to see a working example of your code to determine what it is you're actually doing.
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.