javaswinggraphicsjpanelpaintcomponent

Java JPanel getGraphics()


Since Java only supports single inheritance, I desire to paint directly on an instance of a JPanel that is a member of the class Panel. I grab an instance of Graphics from the member and then paint whatever I desire onto it.

How can I not inherit from JComponent or JPanel and still utilize getGraphics() for painting on this without overriding public void paintComponent(Graphics g)?

private class Panel {
      private JPanel panel;
      private Graphics g;

      public Panel() {
           panel = new JPanel();
      }

      public void draw() {
           g = panel.getGraphics();
           g.setColor(Color.CYAN);
           g.draw(Some Component);
           panel.repaint();
      }
}

The panel is added to a JFrame that is made visible prior to calling panel.draw(). This approach is not working for me and, although I already know how to paint custom components by inheriting from JPanel and overriding public void paintComponent(Graphics g), I did not want to inherit from JPanel.


Solution

  • Here are some very simple examples which show how to paint outside paintComponent.

    The drawing actually happens on a java.awt.image.BufferedImage, and we can do that anywhere, as long as we're on the Event Dispatch Thread. (For discussion of multithreading with Swing, see here and here.)

    Then, I'm overriding paintComponent, but only to paint the image on to the panel. (I also paint a little swatch in the corner.)

    This way the drawing is actually permanent, and Swing is able to repaint the panel if it needs to without causing a problem for us. We could also do something like save the image to a file easily, if we wanted to.

    PaintAnyTime screenshot

    import javax.swing.*;
    import java.awt.*;
    import java.awt.image.*;
    import java.awt.event.*;
    
    /**
     * Holding left-click draws, and
     * right-clicking cycles the color.
     */
    class PaintAnyTime {
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    new PaintAnyTime();
                }
            });
        }
    
        Color[]    colors = {Color.red, Color.blue, Color.black};
        int  currentColor = 0;
        BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
        Graphics2D  imgG2 = img.createGraphics();
    
        JFrame frame = new JFrame("Paint Any Time");
        JPanel panel = new JPanel() {
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                // Creating a copy of the Graphics
                // so any reconfiguration we do on
                // it doesn't interfere with what
                // Swing is doing.
                Graphics2D g2 = (Graphics2D) g.create();
                // Drawing the image.
                int w = img.getWidth();
                int h = img.getHeight();
                g2.drawImage(img, 0, 0, w, h, null);
                // Drawing a swatch.
                Color color = colors[currentColor];
                g2.setColor(color);
                g2.fillRect(0, 0, 16, 16);
                g2.setColor(Color.black);
                g2.drawRect(-1, -1, 17, 17);
                // At the end, we dispose the
                // Graphics copy we've created
                g2.dispose();
            }
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(img.getWidth(), img.getHeight());
            }
        };
    
        MouseAdapter drawer = new MouseAdapter() {
            boolean rButtonDown;
            Point prev;
    
            @Override
            public void mousePressed(MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton(e)) {
                    prev = e.getPoint();
                }
                if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) {
                    // (This just behaves a little better
                    // than using the mouseClicked event.)
                    rButtonDown  = true;
                    currentColor = (currentColor + 1) % colors.length;
                    panel.repaint();
                }
            }
    
            @Override
            public void mouseDragged(MouseEvent e) {
                if (prev != null) {
                    Point  next = e.getPoint();
                    Color color = colors[currentColor];
                    // We can safely paint to the
                    // image any time we want to.
                    imgG2.setColor(color);
                    imgG2.drawLine(prev.x, prev.y, next.x, next.y);
                    // We just need to repaint the
                    // panel to make sure the
                    // changes are visible
                    // immediately.
                    panel.repaint();
                    prev = next;
                }
            }
    
            @Override
            public void mouseReleased(MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton(e)) {
                    prev = null;
                }
                if (SwingUtilities.isRightMouseButton(e)) {
                    rButtonDown = false;
                }
            }
        };
    
        PaintAnyTime() {
            // RenderingHints let you specify
            // options such as antialiasing.
            imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                RenderingHints.VALUE_ANTIALIAS_ON);
            imgG2.setStroke(new BasicStroke(3));
            //
            panel.setBackground(Color.white);
            panel.addMouseListener(drawer);
            panel.addMouseMotionListener(drawer);
            Cursor cursor =
                Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
            panel.setCursor(cursor);
            frame.setContentPane(panel);
            frame.pack();
            frame.setResizable(false);
            frame.setLocationRelativeTo(null);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        }
    }
    

    It's also possible to set up a JLabel with an ImageIcon, although personally I don't like this method. I don't think JLabel and ImageIcon are required by their specifications to see changes we make to the image after we've passed it to the constructors.

    This way also doesn't let us do stuff like painting the swatch. (For a slightly more complicated paint program, on the level of e.g. MSPaint, we'd want to have a way to select an area and draw a bounding box around it. That's another place we'd want to be able to paint directly on the panel, in addition to drawing to the image.)

    import javax.swing.*;
    import java.awt.*;
    import java.awt.image.*;
    import java.awt.event.*;
    
    /**
     * Holding left-click draws, and
     * right-clicking cycles the color.
     */
    class PaintAnyTime {
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    new PaintAnyTime();
                }
            });
        }
    
        Color[]    colors = {Color.red, Color.blue, Color.black};
        int  currentColor = 0;
        BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
        Graphics2D  imgG2 = img.createGraphics();
    
        JFrame frame = new JFrame("Paint Any Time");
        JLabel label = new JLabel(new ImageIcon(img));
    
        MouseAdapter drawer = new MouseAdapter() {
            boolean rButtonDown;
            Point prev;
    
            @Override
            public void mousePressed(MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton(e)) {
                    prev = e.getPoint();
                }
                if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) {
                    // (This just behaves a little better
                    // than using the mouseClicked event.)
                    rButtonDown  = true;
                    currentColor = (currentColor + 1) % colors.length;
                    label.repaint();
                }
            }
    
            @Override
            public void mouseDragged(MouseEvent e) {
                if (prev != null) {
                    Point  next = e.getPoint();
                    Color color = colors[currentColor];
                    // We can safely paint to the
                    // image any time we want to.
                    imgG2.setColor(color);
                    imgG2.drawLine(prev.x, prev.y, next.x, next.y);
                    // We just need to repaint the
                    // label to make sure the
                    // changes are visible
                    // immediately.
                    label.repaint();
                    prev = next;
                }
            }
    
            @Override
            public void mouseReleased(MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton(e)) {
                    prev = null;
                }
                if (SwingUtilities.isRightMouseButton(e)) {
                    rButtonDown = false;
                }
            }
        };
    
        PaintAnyTime() {
            // RenderingHints let you specify
            // options such as antialiasing.
            imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                RenderingHints.VALUE_ANTIALIAS_ON);
            imgG2.setStroke(new BasicStroke(3));
            //
            label.setPreferredSize(new Dimension(img.getWidth(), img.getHeight()));
            label.setBackground(Color.white);
            label.setOpaque(true);
            label.addMouseListener(drawer);
            label.addMouseMotionListener(drawer);
            Cursor cursor =
                Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
            label.setCursor(cursor);
            frame.add(label, BorderLayout.CENTER);
            frame.pack();
            frame.setResizable(false);
            frame.setLocationRelativeTo(null);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        }
    }