Search code examples
javaswingpanelpaintpaintcomponent

Swing: paintComponenet won't paint to JPanel


I am creating a GUI interface that draws shapes.

I decided to use paintComponent() on a JPanel, instead of using paint() on a JFrame. This is because, when painting on a frame, the buttons wouldn't appear until I moused over them.

CHANGES/UPDATES that I have made recently (updated code below):

  1. I implemented MouseListener and MouseMotionListener in my artPanel JPanel class. It seems like this solved a few of my issues. But there are a few problems:
  2. You can download my java file here so that it is easier for you identify the issues.

PROBLEMS that I can't figure out:

  1. The Line button does not create a line properly. I have been playing around with the mouseListener methods, but I cannot get this to work. I want it to simply draw a line while the mouse is clicked and dragged.
  2. The other buttons draw shapes properly. HOWEVER... The shapes don't stay on the panel when I make a new shape. For example, I make a circle. Then as soon as I make another circle, the first one disappears. I want them all to stay on the canvas at all times.

Other than those two, it works pretty well. A photo of what it looks like is here.

public class Shapes extends JFrame implements ActionListener, WindowListener{

    /**
     * Instance variables are created for the shape's sizes, window height and width.
     */
    int currentX, currentY;
    int previousX, previousY;
    int topLeftX, topLeftY;
    int bottomRightX, bottomRightY;
    int height, width;
    public final int WINDOW_WIDTH = 900;
    public final int WINDOW_HEIGHT = 700;

    /**
     * String shape is created for the type of shape.
     */
    public String shape = "Shape";

    /**
     * Color is set to null, because no color is pressed yet.
     */
    public Color color = null;

    /**
     * MouseReleased is false because no mouse has been released yet.
     */
    public boolean mouseReleased = false;

    private class artPanel extends JPanel implements MouseListener, MouseMotionListener{

        public artPanel(){
         /**
         * Listeners are added to the GUI (for the mouse clicks, and for the window exit).
         */
        addMouseListener(this);
        addMouseMotionListener(this);
        }

        /**
         * mouseDragged method is overridden.
         * Previous X and Y variables are set to the current X and Y values.
         * Then, the current X and Y values are set to the position where the mouse is dragged.
         * The width and height is then calculated, while using the absolute value.
         * Repaint method is invoked.
         */
        @Override
        public void mouseDragged(MouseEvent e) {

            previousX = currentX;
            previousY = currentY;

            currentX = bottomRightX = e.getX();
            currentY = bottomRightY = e.getY();

            width = Math.abs(topLeftX - bottomRightX);
            height = Math.abs(topLeftY - bottomRightY);

            repaint();
        }

        /**
         * mouseClicked method is overridden.
         */
        @Override
        public void mouseClicked(MouseEvent e) {

        }

        /**
         * mouseEntered method is overridden.
         */
        @Override
        public void mouseEntered(MouseEvent e) {

        }

        /**
         * mouseExited method is overridden.
         */
        @Override
        public void mouseExited(MouseEvent e) {

        }

        /**
         * mousePressed method is overridden, current X and Y variables are set to the position where the mouse is pressed.
         */
        @Override
        public void mousePressed(MouseEvent e) {
            topLeftX = currentX = e.getX();
            topLeftY = currentY = e.getY();

        }

        /**
         * mouseReleased method is overridden.
         * Bottom Right X and Y variables are set to the position where the mouse is pressed.
         * Width and height is set using the absolute value of the difference.
         * Repaint method is invoked.
         */
        @Override
        public void mouseReleased(MouseEvent e) {
            bottomRightX = e.getX();
            bottomRightY = e.getY();

            width = Math.abs(topLeftX - bottomRightX);
            height = Math.abs(topLeftY - bottomRightY);

            mouseReleased = true;

            repaint();
        }




        /**
         * mouseMoved method is overridden.
         */
        @Override
        public void mouseMoved(MouseEvent e) {

        }

        /**
         * Paint method is created with parameter g for implementing graphics.
         */
        public void paintComponent(Graphics g){
            super.paintComponent(g);

            /**
             * If the color is not null (has been changed), then the color is set to the user's c
             */
            if(color != null)
                g.setColor(color);

            /**
             * If the shape is a line (line button clicked), then the line is drawn.
             */
            if(shape.equals("Line")){
                g.drawLine(previousX, previousY, currentX, currentY);
            }

            /**
             * If the shape is a circle (circle button clicked), then the circle is drawn.
             * The mouseReleased is set to false so that it draws it when it is dragged.
             */
            else if(shape.equals("Circle") && mouseReleased){
                g.drawOval(topLeftX, topLeftY, width, height);
                mouseReleased = false;
            }

            /**
             * If the shape is a Rectangle (rectangle button clicked), then the rectangle is drawn.
             * The mouseReleased is set to false so that it draws it when it is dragged.
             */
            else if(shape.equals("Rectangle") && mouseReleased){
                g.drawRect(topLeftX, topLeftY, width, height);
                mouseReleased = false;
            }

            /**
             * If the shape is an Arc (arc button clicked), then the arc is drawn.
             * The mouseReleased is set to false so that it draws it when it is dragged.
             */
            else if(shape.equals("Arc") && mouseReleased){
                g.drawArc(topLeftX, topLeftY, width, height, 0, 90);
                mouseReleased = false;
            }
        }

    }


    /**
     * Constructor for creating the GUI
     */
    public Shapes(){
        /**
         * Super is invoked, title is set
         */
        super("Draw Geometric Shapes");
        /**
         * Size is set using the instance variables, does nothing on close as default.
         */
        setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

        /**
         * Layout is set to borderlayout for the frame.
         */
        setLayout(new BorderLayout());

        /**
         * A panel for the buttons is created, uses a flowlayout.
         */
        JPanel buttons = new JPanel();
        buttons.setLayout(new FlowLayout());

        /**
         * Button for the color is created.
         */
        JButton colorChooser = new JButton("Color");
        colorChooser.addActionListener(this);
        buttons.add(colorChooser);

        /**
         * Button for making a line is created.
         */
        JButton line = new JButton("Line");
        line.addActionListener(this);
        buttons.add(line);

        /**
         * Button for making a circle is created.
         */
        JButton circle = new JButton("Circle");
        circle.addActionListener(this);
        buttons.add(circle);

        /**
         * Button for making a rectangle is created.
         */
        JButton rect = new JButton("Rectangle");
        rect.addActionListener(this);
        buttons.add(rect);

        /**
         * Button for making an arc is created
         */
        JButton arc = new JButton("Arc");
        arc.addActionListener(this);
        buttons.add(arc);

        /**
         * Buttons panel is added to the south part of the border layout (bottom of the frame).
         */
        add(buttons, BorderLayout.SOUTH);

        addWindowListener(this);

        artPanel p = new artPanel();
        p.setLayout(new FlowLayout());
        p.setBackground(Color.WHITE);

        add(p);
    }


    /**
     * @param args
     */
    public static void main(String[] args) {
        /**
         * New object of type Shapes is created and set to visible.
         */
        Shapes draw_shapes = new Shapes();
        draw_shapes.setVisible(true);
    }


    /**
     * actionPerformed is overridden and each button is set to a shape type
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        /**
         *  If the button "Color" is clicked, then the color chooser dialog appears.
         */
        if(e.getActionCommand().equals("Color")){
            color = JColorChooser.showDialog(this, "Color Chooser", color);
        }

        /**
         *  If the button "Line" is clicked, then the shape is set to Line.
         */
        else if(e.getActionCommand().equals("Line")){
            if(shape.equalsIgnoreCase("Line")){
                shape = "Line";
            }
            else shape = "Line";
        }

        /**
         *  If the button "Circle" is clicked, then the shape is set to circle.
         */
        else if(e.getActionCommand().equals("Circle")){
            if(shape.equalsIgnoreCase("Circle")){
                shape = "Circle";
            }
            else shape = "Circle";
        }

        /**
         *  If the button "Rectangle" is clicked, then the shape is set to rectangle.
         */
        else if(e.getActionCommand().equals("Rectangle")){
            if(shape.equalsIgnoreCase("Rectangle")){
                shape = "Rectangle";
            }
            else shape = "Rectangle";
        }

        /**
         *  If the button "Arc" is clicked, then the shape is set to Arc.
         */
        else if(e.getActionCommand().equals("Arc")){
            if(shape.equalsIgnoreCase("Arc")){
                shape = "Arc";
            }
            else shape = "Arc";
        }
    }

    }
}

I cut out the rest because they were windowlistener methods.


Solution

  • add(buttons, BorderLayout.SOUTH);
    
    Panel p = new Panel();
    add(p);
    
    JPanel whitePanel = new JPanel();
    whitePanel.setBackground(Color.WHITE);
    add(whitePanel);
    

    You code implies you using the default layout manager of the content panel of the frame which is a BorderLayout.

    You attempt to add "p" and "whitePanel" to the CENTER (which is the default when you don't specify a constraint) of the BorderLayout . Only one component can be added to a given location of the BorderLayout. Only the last panel added is painted, which is your whitePanel so you will never see the custom painting of your panel.

    Don't know what your exact layout requirement is so I can't suggest a solution other than to use a different layout manager or use nested panels with different layout managers.

    Also, when doing custom painting you should be overriding the getPreferredSize() method to return the size so the layout managers can do their job.

    Don't call your class "Panel", there is already an AWT class by that name so this causes confusion. Your class name should be more descriptive.

    I decided to use paintComponent() on a JPanel, instead of using paint() on a JFrame.

    You should never override paint on a frame. You should indeed be overriding paintComponent() of a JPanel. Read the section from the Swing tutorial on Custom Painting for more information and working examples.

    Edit:

    Then as soon as I make another circle, the first one disappears. I want them all to stay on the canvas at all times.

    Read Custom Painting Approaches which show 2 ways to do the painting when you want multiple objects painted on the same panel. Which approach you choose depends on your exact requirement.