0

I have these two classes:

public class Pencil extends JComponent implements MouseListener, MouseMotionListener{
Plansa plansa;
Graphics g;
public Pencil(Plansa newCanvas){
    this.plansa = newCanvas;
    this.plansa.setFlagShape(false);
}
@Override
public void mouseDragged(MouseEvent arg0) {
    plansa.setMouseDragged(arg0);
    this.plansa.setFlagShape(false);
    plansa.paintComponent(plansa.getGraphics());
}

@Override
public void mouseClicked(MouseEvent e) {
}

@Override
public void mousePressed(MouseEvent arg0) {
    plansa.setMousePressed(arg0);
}

@Override
public void mouseReleased(MouseEvent arg0) {
    // TODO Auto-generated method stub
    plansa.setMouseReleased(arg0);
    this.plansa.setFlagShape(true);
    plansa.paintComponent(plansa.getGraphics());

}

@Override
public void mouseEntered(MouseEvent e) {
}

@Override
public void mouseExited(MouseEvent e) {
}

@Override
public void mouseMoved(MouseEvent e) {
}

}

And this one:

public class Plansa extends JPanel{
Image image;
Pencil pencil;
 //...
 void init(){
    this.setShape("freeLine");
    this.setColor(Color.BLACK);
    this.size=1;
    this.type="round";
    this.fill=false;
    this.setBackground(Color.WHITE);
    pencil = new Pencil(this);
    addMouseListener(pencil);
    addMouseMotionListener(pencil);
    flagPaint = true;
    flagShape = false;
}
public Plansa(){
    this.setSize(800, 600);
    init();
}

//...

       @Override
    public void paintComponent(Graphics g) {
    g.setColor(currentColor);
    Graphics2D g2d = (Graphics2D) g;
    g2d.setStroke(setBrush(size,type));
    super.paintComponent(g);
    switch(shape){
        default: break;
        case "freeLine":{ 
            g.drawLine(xDragged, yDragged, xCurrent, yCurrent); 
            break;
        }
            case "rectangle":{
                if(flagShape == true){
            g.drawRect(xPressed, yPressed, Math.max(xCurrent-xPressed,xPressed-xCurrent), Math.max(yCurrent-yPressed,yPressed-yCurrent));
            if(fill == true) g.fillRect(xPressed, yPressed, Math.max(xCurrent-xPressed,xPressed-xCurrent), Math.max(yCurrent-yPressed,yPressed-yCurrent));
                }
            break;
        }
        case "circle":{
            if(flagShape == true){
            int radius = (int)Math.sqrt(Math.max(xCurrent-xPressed,xPressed-xCurrent)*Math.max(xCurrent-xPressed,xPressed-xCurrent)+Math.max(yCurrent-yPressed,yPressed-yCurrent)*Math.max(yCurrent-yPressed,yPressed-yCurrent));
            g.drawOval(xPressed, yPressed, radius, radius);
            if(fill == true) g.fillOval(xPressed, yPressed, radius, radius);
            }
            break;
        }
        case "oval":{
            if(flagShape == true){
            g.drawOval(xPressed, yPressed, Math.max(xCurrent-xPressed,xPressed-xCurrent), Math.max(yCurrent-yPressed,yPressed-yCurrent));
            if(fill == true) g.fillOval(xPressed, yPressed, Math.max(xCurrent-xPressed,xPressed-xCurrent), Math.max(yCurrent-yPressed,yPressed-yCurrent));
            }
            break;
        }
        case "line":{
            if(flagShape == true){
            g.drawLine(xPressed, yPressed, xCurrent, yCurrent);
            }
            break;
        }
    }

}

 //...
}

My problem is that every time I call paintComponent() method, the JPanel clears and the only item that remains is the one I just drawn. Is there any way to avoid this?

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Diana
  • 11
  • 2
  • 5
  • 4
    You should NEVER call `paintComponent` yourself. This is part of the paint chain and is called on your behalf by the RepaintManager (via the Event Dispatching Thread). Instead, you should be using `repaint` which makes a request to the RepaintManager to schedule an update at some time in the future. You should also NEVER use `getGraphics`. It is possible for this method to return null and is simply a snap shot of the last paint cycle. It will be erased on the next paint cycle – MadProgrammer Apr 08 '13 at 23:21
  • The question that comes to my mind is, why arn't you just adding `Plansa` directly to the parent component and using a `null`/absolute layout manager to layout out the components. The rest would be taken care for you. Also, remember, painting is stateless. That is, what ever you want painted on each paint cycle must be re-painted within one of the approptiate paint methods – MadProgrammer Apr 08 '13 at 23:24
  • I'll consider this, thank you. – Diana Apr 08 '13 at 23:29
  • 1
    A better idea generally for progressive drawings is to use a `BufferedImage` as the canvas, as seen [here](http://stackoverflow.com/a/10628553/418556). – Andrew Thompson Apr 08 '13 at 23:31
  • @Andrew Thompson I tried this and it works fine, thanks. – Diana Apr 09 '13 at 01:07

4 Answers4

4

After close inspection of the code, it appears that you're trying to use Components as "painters", it's a bit like using a sports car as a go-kart, a lot of extras for little gain.

Instead, you should define yourself a interface of some kine that provides the basic requirements of what you want to draw/paint and then define concrete class implementations that implement the functionality.

You would then maintain a List of some kind of all the drawings that can be painted.

While simple, the example below provides a jumping off point that could be enhanced that allow selection of drawings, re-ordering and removal (should you want it).

This, is, essentially, the same concept proposed by Doorknob (+1)

enter image description here

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestDraw {

    public static void main(String[] args) {
        new TestDraw();
    }

    public TestDraw() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new Pencil());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class Pencil extends JPanel implements MouseListener, MouseMotionListener {

        private List<Drawable> drawables;
        private Drawable activeDrawable;

        private Point clickPoint;

        public Pencil() {
            drawables = new ArrayList<>(5);
            addMouseListener(this);
            addMouseMotionListener(this);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            for (Drawable drawable : drawables) {
                drawable.paint(g2d);
            }
            g2d.dispose();
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (activeDrawable != null) {
                Point p = e.getPoint();
                Rectangle bounds = activeDrawable.getBounds();

                int x = bounds.x;
                int y = bounds.y;
                int width = p.x - clickPoint.x;
                int height = p.y - clickPoint.y;
                if (width < 0) {
                    width *= -1;
                    x = p.x;
                }
                if (height < 0) {
                    height *= -1;
                    y = p.y;
                }
                bounds = new Rectangle(x, y, width, height);
                System.out.println(bounds);
                activeDrawable.setBounds(bounds);
                repaint();
            }
        }

        @Override
        public void mouseClicked(MouseEvent e) {
        }

        protected Drawable createActiveShape(MouseEvent e) {

            System.out.println("Anchor = " + e.getPoint());
            Drawable drawable = new FreeLine(e.getPoint());
            drawable.setLocation(e.getPoint());
            return drawable;

        }

        @Override
        public void mousePressed(MouseEvent e) {
            // You could also check to see if the clicked on a drawable...
            clickPoint = e.getPoint();
            activeDrawable = createActiveShape(e);
            drawables.add(activeDrawable);
            repaint();
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (activeDrawable != null) {
                Rectangle bounds = activeDrawable.getBounds();
                if (bounds.width == 0 || bounds.height == 0) {
                    drawables.remove(activeDrawable);
                }
            }
            clickPoint = null;
            activeDrawable = null;
        }

        @Override
        public void mouseEntered(MouseEvent e) {
        }

        @Override
        public void mouseExited(MouseEvent e) {
        }

        @Override
        public void mouseMoved(MouseEvent e) {
        }
    }

    public interface Drawable {

        public void setLocation(Point p);

        public void setSize(Dimension dim);

        public void setBounds(Rectangle bounds);

        public Rectangle getBounds();

        public void paint(Graphics2D g2d);
    }

    public abstract class AbstractDrawable implements Drawable {

        private Rectangle bounds;

        public AbstractDrawable() {
            bounds = new Rectangle();
        }

        @Override
        public void setLocation(Point p) {
            bounds.setLocation(p);
        }

        @Override
        public void setBounds(Rectangle bounds) {
            this.bounds = bounds;
        }

        @Override
        public void setSize(Dimension dim) {
            bounds.setSize(dim);
        }

        @Override
        public Rectangle getBounds() {
            return bounds;
        }
    }

    public class FreeLine extends AbstractDrawable {

        private Point anchor;

        public FreeLine(Point anchor) {
            this.anchor = anchor;
        }

        @Override
        public void paint(Graphics2D g2d) {
            Rectangle bounds = getBounds();
            Point p1 = new Point(anchor);
            Point p2 = new Point(bounds.getLocation());
            if (p1.x > p2.x) {
                p2.x = p1.x - bounds.width;
            } else {
                p2.x = p1.x + bounds.width;
            }
            if (p1.y > p2.y) {
                p2.y = p1.y - bounds.height;
            } else {
                p2.y = p1.y + bounds.height;
            }
            g2d.draw(new Line2D.Float(p1, p2));
        }
    }
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
3

Store all the objects in an ArrayList and iterate through it when you are drawing.

You could have them all implement a custom interface, Drawable for example, then store in an ArrayList<Drawable>.

tckmn
  • 57,719
  • 27
  • 114
  • 156
3

A better idea generally for progressive drawings is to use a BufferedImage as the canvas, as seen here.

Community
  • 1
  • 1
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
0

Don't use/comment out:

super.paintComponent(g);

That line is the one that is doing the clearing.

tckmn
  • 57,719
  • 27
  • 114
  • 156
  • 1
    I think it should be further stressed that this suggestion is just to observe the difference between codes, and **all** real world implementations ***should*** call `super.paintComponent(g);` – Andrew Thompson Apr 08 '13 at 23:23
  • I thought of this too, but didn't understand how to do it, what data type to use for the objects. Should I create a new class, or use something default? – Diana Apr 08 '13 at 23:23