7

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 Grahics 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.

mKorbel
  • 108,320
  • 17
  • 126
  • 296
Mushy
  • 2,225
  • 9
  • 30
  • 50
  • Basically you shouldn't use `getGraphics`. The Graphics context returned from it is not the same one that is handed to paintComponent. In your case, probably this doesn't work because calling repaint erases whatever you painted. Can you explain what you are trying to achieve? As it is, this seems like an [XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Radiodef Jan 24 '14 at 00:54
  • For example [here is a similar question](http://stackoverflow.com/questions/15986677/drawing-an-object-using-getgraphics-without-extending-jframe) but you've said specifically you don't want to extend JPanel and override paintComponent. It would be helpful to explain what you're doing and why so solutions can be suggested. – Radiodef Jan 24 '14 at 01:00
  • This isn't an [XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) because I already know the solution to my problem. I just want to know how using the above methodology. – Mushy Jan 24 '14 at 01:02
  • You shouldn't use getGraphics. To be honest I am not even really sure why it exists in the API. As soon as the panel gets repainted (such as can happen automatically and 100% outside your control) the painting done with getGraphics will disappear. Usually the solution is to say "don't use getGraphics, override paintComponent", but you've said you don't want that. – Radiodef Jan 24 '14 at 01:10
  • @Radiodef Then I suppose there is no way to accomplish what I ask. – Mushy Jan 24 '14 at 01:13
  • 1
    You can easily, for example, create a BufferedImage to draw on to outside of paintComponent and then paint the image on the JPanel. But you just haven't said what you're actually trying to do. – Radiodef Jan 24 '14 at 01:16
  • @Radiodef You'd still need something to paint it to the screen, `JLabel` would be able to do it out of the box... – MadProgrammer Jan 24 '14 at 01:18

2 Answers2

6

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);
    }
}
Community
  • 1
  • 1
Radiodef
  • 35,285
  • 14
  • 78
  • 114
-1
class SomeComponent extends JComponent {

    private Graphics2D g2d;

    public void paintComponent(Graphics g) {
        g2d = (Graphics2D) g.create();
        g2d.setColor(Color.BLACK);
        g2d.scale(scale, scale);
        g2d.drawOval(0, 0, importance, importance);

    }

    public Graphics2D getG2d() {
        return g2d;
    }
    public void setG2d(Graphics2D g2d) {
        this.g2d = g2d;
    }
}

then you can do the following get the SomeComponent instance in the panel and modify it

Graphics2D x= v.getPanel().get(i).getG2d;
x.setColor(Color.BLUE);
v.getPanel().get(i).setG2d(x);
v.getPanel().repaint();
v.getPanel().revalidate();

V is a class that extends JFrame and contains the panel in it AND i is instance of SomeComponent

Exorcismus
  • 1,672
  • 16
  • 44
  • I don't want to inherit from `JComponent` or `JPanel` while still painting on a local instance of `JPanel` residing in my class `Panel`. – Mushy Jan 24 '14 at 01:11
  • well I had your problem before , and this was the only way I found to enable editing paintComponent() method ,and here your not inheriting from JPanel , your doing your own paint class – Exorcismus Jan 24 '14 at 01:14
  • Doesn't look like there is a way to do it without the use of inheritance and overriding paintComponent(). – Mushy Jan 24 '14 at 01:15
  • 2
    @Musy This is not how painting works in Swing. Take a look at [Performing Custom Painting](http://docs.oracle.com/javase/tutorial/uiswing/painting/) and [Painting in AWT and Swing](http://www.oracle.com/technetwork/java/painting-140037.html) for more details about how the painting process works – MadProgrammer Jan 24 '14 at 01:16
  • 1
    You should never maintain a reference to any `Graphics` context you did not create yourself. Swing uses a passive painting approach which means that paints can occur at any time, as deemed by the repaint manager, and anything that was previously painted will be lost... – MadProgrammer Jan 24 '14 at 01:18