2

Okay, here I go again.

I have a JPanel on which I have overridden paintComponent() and drawn on a custom bufferedImage to provide my JPanel with a background image. I also have a JButton which I have done the same with, in order to create a button with a custom shape. The problem is, although the JButton appears to be added to the JPanel with the background image properly, it is not drawn properly.

I can get input from the button as if it actually existed, but it doesn't actually show up, while it does on a standard JPanel. An example is below.

Thanks in advance for any help.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class Test {
    public static void main (String[] args){
        final JFrame frame = new JFrame();
        DrawPanel panel1 = new DrawPanel(createBufferedImage("background.png"));
        JPanel panel2 = new JPanel();

        DrawButton button1 = new DrawButton(createBufferedImage("button.png"));
        button1.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent ae){
                JOptionPane.showMessageDialog(frame, "input detected, button1");
            }
        });

        DrawButton button2 = new DrawButton(createBufferedImage("button.png"));
        button2.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent ae){
                JOptionPane.showMessageDialog(frame, "input detected, button2");
            }
        });

        panel1.add(button1);
        panel2.add(button2);
        frame.getContentPane().add(panel1, BorderLayout.NORTH);
        frame.getContentPane().add(panel2, BorderLayout.CENTER);
        frame.validate();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }

    protected static BufferedImage createBufferedImage(String path){
        File img = new File(path);
        BufferedImage bi = null;
        try{
            bi = ImageIO.read(img);
        }
        catch (IOException ioe){
            throw new RuntimeException();
        }
        BufferedImage newImage = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = newImage.createGraphics();
        g2d.drawImage(bi, 0, 0, null);
        g2d.dispose();  
        return newImage;
    }
}

class DrawPanel extends JPanel{

    private BufferedImage bg; 

    public DrawPanel(BufferedImage bg){
        this.bg = bg;
        new JPanel();
        setPreferredSize(new Dimension(bg.getWidth(), bg.getHeight()));
    }

    @Override
    protected void paintComponent(Graphics g){
        super.paintComponent(g);
        g.drawImage(bg, 0, 0, null);
        g.dispose();
    }
}

class DrawButton extends JButton{

    private BufferedImage bi;

    public DrawButton(BufferedImage bi){
        setPreferredSize(new Dimension(bi.getWidth(), bi.getHeight()));
        this.bi = (BufferedImage) bi;
        setContentAreaFilled(false);
    }

    @Override
    public Dimension getPreferredSize(){
        return new Dimension(bi.getWidth(), bi.getHeight());
    }

    public BufferedImage getIconImage(){
        return bi;
    }

    @Override
    protected void paintComponent(Graphics g){
        super.paintComponent(g);
        g.drawImage(bi, 0, 0, null);
        g.dispose();
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
user18
  • 119
  • 7
  • What is the purpose of calling `new JPanel();` in the DrawPanel constructor? Why create an object that is never needed, never used, never assigned to a variable and so simply disposed of when the constructor goes out of scope? – Hovercraft Full Of Eels Dec 12 '12 at 02:35
  • @HovercraftFullOfEels There is...no purpose whatsoever. I think that originally the DrawPanel was just a JPanel, as opposed to an object, and I never got rid of the call. Oops. – user18 Dec 12 '12 at 02:40
  • `class DrawButton extends JButton{` Don't do that, instead just add an icon to it, `setContentAreaFilled(false)` & `setBorder(null)`. – Andrew Thompson Dec 12 '12 at 03:55
  • @AndrewThompson I need to create DrawButton as its own class because of how I'm using the button. See this question for more: [link](http://stackoverflow.com/questions/13825123/event-detection-on-opaque-pixels-in-jbutton). I didn't see the need to explain specifically _why_ I was doing it that way, because the why didn't seem relevant to the issue at hand, only that it was an issue. I know about the method you suggest, and I had already dismissed it as inappropriate for my use case. – user18 Dec 12 '12 at 04:06

1 Answers1

4

Never call dispose on the Graphics object given to you by the JVM, only on Graphics objects you yourself create.

So this is OK:

    BufferedImage newImage = new BufferedImage(bi.getWidth(), bi.getHeight(), 
         BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = newImage.createGraphics();
    g2d.drawImage(bi, 0, 0, null);
    g2d.dispose();

But this isn't:

protected void paintComponent(Graphics g){
    super.paintComponent(g);
    g.drawImage(bg, 0, 0, null);
    g.dispose();
}

The JVM may (and likely will) need to use that object for further drawing including the drawing of a container's child components, and if you dispose of it, you can prevent this from happening.

Another suggestion: rather than setPreferredSize, consider overriding getPreferredSize. So rather than this:

setPreferredSize(new Dimension(bg.getWidth(), bg.getHeight()));

Consider:

@Override
public Dimension getPreferredSize() {
  if (bg == null) {
    return super.getPreferredSize();
  } else {
    return new Dimension(bg.getWidth(), bg.getHeight());
  }
}

This way the component's preferred size is reliably set and any user of the component can't change it.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • Hey! That fixes it! Thanks! I had a feeling it was something really simple that I was doing wrong, thanks for pointing it out. I'll try to remember that. – user18 Dec 12 '12 at 02:37
  • @user18: you're welcome. Again though, why the `new JPanel();` line? – Hovercraft Full Of Eels Dec 12 '12 at 02:38
  • Just saw that comment above -- the `new JPanel();` was just a goof. – user18 Dec 12 '12 at 02:41
  • Oh hey, that's interesting. Good to know, and I'll consider that. They say you learn something new every day -- looks like I'm set for the rest of the week now. Thanks again. – user18 Dec 12 '12 at 02:53