Search code examples
javaswingjpaneljbuttonpaintcomponent

Java: Why is the background image painted over the buttons?


Is it possible to paint update image behind buttons in the same JPanel? I'm trying to get a Menu screen working but when I try to put in a paint loop to update the background image the buttons disappear. Note: These are custom buttons, they are invisible except for a custom image that I put in them using JButton.setIcon if that makes any difference in the rendering.

public class MenuPanel extends JPanel{
    BufferedImage image;
    private int height =Toolkit.getDefaultToolkit().getScreenSize().height-37;
    private int width = Toolkit.getDefaultToolkit().getScreenSize().width;
    private JButton optionsButton = new JButton("Options");
    private JButton startButton = new JButton ("Start");

    public MenuPanel() {
        setLayout(null);
        setSize(width, height);

        try {image = ImageIO.read(new File("stuyrim.png"));}
        catch (Exception e) {Utilities.showErrorMessage(this, e);}

        optionsButton.addActionListener(e -> {
        //      optionsButton.getParent().getParent();
            });
        optionsButton.setOpaque(false);
        optionsButton.setBorderPainted(false);
        optionsButton.setContentAreaFilled(false);
        optionsButton.setSize(width/10,height/20);
        optionsButton.setVerticalTextPosition(SwingConstants.CENTER);
        optionsButton.setHorizontalTextPosition(SwingConstants.CENTER);
        optionsButton.setFont(new Font("TimesRoman", Font.PLAIN, 20));
        optionsButton.setLocation((width/3)-(width/20), (height/7*6)-(height/40));
        Image img = new ImageIcon("GUI Images/Button.png").getImage().getScaledInstance
            (optionsButton.getWidth(),optionsButton.getHeight(),java.awt.Image.SCALE_SMOOTH);
        optionsButton.setIcon(new ImageIcon(img));
        optionsButton.setForeground(Color.white);


        startButton.addActionListener(e -> {
        startButton.getParent().getParent().add(new Screen());
        startButton.getParent().getParent().add(new GamePanel());
        Graphics g = getGraphics();
        g.clearRect(0,0,width,height);
        startButton.getParent().getParent().remove(this);
        g.dispose();
            });
        startButton.setOpaque(false);
        startButton.setBorderPainted(false);
        startButton.setContentAreaFilled(false);
        startButton.setSize(width/10,height/20);
        startButton.setVerticalTextPosition(SwingConstants.CENTER);
        startButton.setHorizontalTextPosition(SwingConstants.CENTER);
        startButton.setFont(new Font("TimesRoman",Font.PLAIN, 20));
        startButton.setLocation((width/3*2)-(width/20), (height/7*6)-(height/40));
        Image img1 = new ImageIcon("GUI Images/Button.png").getImage().getScaledInstance
            (optionsButton.getWidth(),optionsButton.getHeight(),java.awt.Image.SCALE_SMOOTH);
        startButton.setIcon(new ImageIcon(img1));
        startButton.setForeground(Color.white);
        add(optionsButton);
        add(startButton);
        setVisible(true);
        revalidate();
        repaint();
    }

    public void paintComponent(Graphics g) {
        g.drawImage(image,0,0,width,height, null);
        g.dispose();
    }

}

Solution

  • There are two basic problems...

    The first, as already highlighted by System.exit, you should be calling super.paintComponent before performing any custom painting.

    The reason for this is that the Graphics context passed to paintComponent is a shared resource. Everything that was painted before your component and everything that is painted after it will share the same Graphics context.

    One of the jobs of paintComponent is to prepare the Graphics context for painting (typically by filling the required space with the background color of the component).

    The second is the use of getGraphics. getGraphics gets a reference to the last Graphics context used to paint the component (and can be null if the component hasn't been painted yet), if you use this you painting outside Swings defined paint chain, which can produce random and unpredictable results.

    Swing uses a passive rendering algorithm, that is, painting occurs at irregular intervals and can occur for any number of reasons, many of which you don't control or don't initiate.

    All custom painting should be done within Swing's defined paint chain, by convention, this is typically done by overriding the paintComponent method.

    Take a look at Performing Custom Painting and Painting in AWT and Swing for more details