Search code examples
javaswinggraphicspaintjcomponent

Why is my JComponent paint(Graphics g) not working/ not being called?


I have been trying to code this application that makes polygons with points. When I have tried to run it at first, I thought that there was a problem with my points. But when I started messing around, looking for the problem, I realized that my paint method in a JComponent isn't even being called. What should I do to fix this?

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JToggleButton;

public class PolygonBuilder extends JFrame{

    final static int WIDTH = 1280;
    final static int HEIGHT = 640;
    public static int xPointPos, yPointPos;
    public static void main(String[] args) {
        new PolygonBuilder();
    }
    public PolygonBuilder() {
        setTitle("Build a Polygon");
        this.setSize(WIDTH,HEIGHT);
        setResizable(false);
        JLabel placeholder = new JLabel();
        BuilderDrawingPanel BuilderPanel = new BuilderDrawingPanel();
        this.add(BuilderPanel, BorderLayout.CENTER);

        JToggleButton DotOnOff = new JToggleButton("Dot");
        DotOnOff.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent arg0) {
                // TODO Auto-generated method stub
                if(DotOnOff.isSelected()) {
                    System.out.println("ON");
                } else {
                    System.out.println("OFF");
                }
            }
        });

        DotOnOff.setBounds(100, 100, 90, 35);

        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
        executor.scheduleAtFixedRate(new RepaintTheBoard(this), 0L, 20L, TimeUnit.MILLISECONDS);

        addMouseMotionListener(new MouseMotionListener() {
            @Override
            public void mouseDragged(MouseEvent arg0) {}

            @Override
            public void mouseMoved(MouseEvent arg0) {
                Point p = MouseInfo.getPointerInfo().getLocation();
                xPointPos = (int) p.getX();
                yPointPos = (int) p.getY();
                System.out.println(xPointPos+","+yPointPos);
            }
        });

        this.add(DotOnOff);
        this.add(placeholder);
        setVisible(true);
    }
}
class RepaintTheBoard implements Runnable {
    PolygonBuilder theBuilder;

    public RepaintTheBoard(PolygonBuilder theBuilder) {
        this.theBuilder = theBuilder;
    }

    @Override
    public void run() {
        theBuilder.repaint();
    }
}

@SuppressWarnings("serial")
class BuilderDrawingPanel extends JComponent{
    static Graphics2D graphicSettings;
    static Shape drawLine;
    public BuilderDrawingPanel() {
        //stuff to draw at start
        System.out.println("Drawing..");
        drawLine =  new Line2D.Float(480,480,0,0);
    }
    @Override
    public void paint(Graphics g) {

        graphicSettings = (Graphics2D)g;
        System.out.println("Drawing..");
        graphicSettings.setColor(Color.RED);
        graphicSettings.fillRect(0, 0, getWidth(), getHeight());
        graphicSettings.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphicSettings.setPaint(Color.BLUE);
        graphicSettings.draw(drawLine);
    }
}

Solution

  • While c0der has provided some valuable information and links, I feel there are aspects of this that should be addressed in an answer.

    • The default layout of (the content pane of) a JFrame is a BorderLayout.
    • Any component added to a border layout with no constraint defaults to the CENTER.
    • Each area (layout constraint) of a border layout can show exactly one component.

    So then we come to this part of the code:

    this.add(DotOnOff);
    this.add(placeholder);
    

    The JVM will try to add them to the center of the content pane to which the builder drawing panel has already been added. So that's why it does not even appear (or get painted).

    Other tips:

    • In any JComponent, the correct method to override for custom painting is the paintComponent(Graphics) method.
    • Any overridden paint method should immediately call the super method, to ensure the background, borders and other 'automatic' elements are painted.
    • A Graphics object is transient, so should not be stored as an attribute of the class, and should certainly not be declared as static.
    • Speaking of static declarations, they are (in GUIs at least) more often a source of problems than solutions. Don't declare GUI attributes as static unless you can explain why it makes sense to do so.
    • Please learn common Java nomenclature (naming conventions - e.g. EachWordUpperCaseClass, firstWordLowerCaseMethod(), firstWordLowerCaseAttribute unless it is an UPPER_CASE_CONSTANT) and use it consistently.

    Update

    This is the source seen in the question, with the most immediate problem fixed. It does not change much of the other stuff recommended, nor does it touch on other problems I saw when working with the code itself. Look at the comments for the things that were changed to make it work.

    This is how it looks here, with the changes:

    enter image description here

    import java.awt.*;
    import java.awt.event.*;
    import java.awt.geom.Line2D;
    import java.util.concurrent.*;
    import javax.swing.*;
    
    public class PolygonBuilder extends JFrame{
    
        final static int WIDTH = 640;
        final static int HEIGHT = 320;
        public static int xPointPos, yPointPos;
        public static void main(String[] args) {
            new PolygonBuilder();
        }
        public PolygonBuilder() {
            setTitle("Build a Polygon");
            // let's be nice to the user..
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            // this is almost certainly not the correct size, 
            // but in itself could deserve a question
            this.setSize(WIDTH,HEIGHT);
            setResizable(false);
            JLabel placeholder = new JLabel("Label!");
            BuilderDrawingPanel BuilderPanel = new BuilderDrawingPanel();
            this.add(BuilderPanel, BorderLayout.CENTER);
    
            JToggleButton DotOnOff = new JToggleButton("Dot");
            DotOnOff.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent arg0) {
                    if(DotOnOff.isSelected()) {
                        System.out.println("ON");
                    } else {
                        System.out.println("OFF");
                    }
                }
            });
    
            DotOnOff.setBounds(100, 100, 90, 35);
    
            ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
            executor.scheduleAtFixedRate(new RepaintTheBoard(this), 0L, 20L, TimeUnit.MILLISECONDS);
    
            addMouseMotionListener(new MouseMotionListener() {
                @Override
                public void mouseDragged(MouseEvent arg0) {}
    
                @Override
                public void mouseMoved(MouseEvent arg0) {
                    Point p = MouseInfo.getPointerInfo().getLocation();
                    xPointPos = (int) p.getX();
                    yPointPos = (int) p.getY();
                    System.out.println(xPointPos+","+yPointPos);
                }
            });
    
            // create a new panel for these buttons..
            //this.add(DotOnOff);
            //this.add(placeholder);
            JPanel buttonPanel = new JPanel(); // default flow layout
            buttonPanel.add(DotOnOff);
            buttonPanel.add(placeholder);
            // now add that panel above the builder panel
            this.add(buttonPanel, BorderLayout.PAGE_START);
            setVisible(true);
        }
    }
    class RepaintTheBoard implements Runnable {
        PolygonBuilder theBuilder;
    
        public RepaintTheBoard(PolygonBuilder theBuilder) {
            this.theBuilder = theBuilder;
        }
    
        @Override
        public void run() {
            theBuilder.repaint();
        }
    }
    
    @SuppressWarnings("serial")
    class BuilderDrawingPanel extends JComponent{
        static Graphics2D graphicSettings;
        static Shape drawLine;
        public BuilderDrawingPanel() {
            //stuff to draw at start
            System.out.println("Drawing..");
            drawLine =  new Line2D.Float(480,480,0,0);
        }
        @Override
        public void paint(Graphics g) {
    
            graphicSettings = (Graphics2D)g;
            System.out.println("Drawing..");
            graphicSettings.setColor(Color.RED);
            graphicSettings.fillRect(0, 0, getWidth(), getHeight());
            graphicSettings.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            graphicSettings.setPaint(Color.BLUE);
            graphicSettings.draw(drawLine);
        }
    }