Search code examples
javaswingjlabelrepaintimageicon

Updating an Image Icon in a JLabel in Swing? Using revalidate() and repaint() but not working


Okay, I'm just getting used to OOP and am just now learning swing. I'm making a simple app that's a 2x2 grid of 4 images (an X, an O, a Square, and a Triangle) and clicking any one switches the color's shape to blue.

I cannot get it to switch to the new image though, and I think it has something to do with something fundamental to my program.

Mind taking a look?

JFrame class:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.ImageIcon;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import javax.swing.JPanel;

public class Frame1 {

    public JFrame frame;

        Frame1 window = new Frame1();
        window.frame.setVisible(true);

    }

    public Frame1() {
        initialize();
    }

    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 900, 900);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(null);

        Squares x = new Squares("images\\black-X.png", "images\\blue-X.png", 0, 0, 450, 450, "x");
        Squares o = new Squares("images\\black-O.png", "images\\blue-O.png", 450, 0, 450, 450, "o");
        Squares sq = new Squares("images\\black-sq.png", "images\\blue-sq.png", 0, 425, 450, 450, "sq");
        Squares tri = new Squares("images\\black-tri.png", "images\\blue-tri.png", 450, 410, 450, 450, "tri");


        frame.getContentPane().add(x.getLabel());
        frame.getContentPane().add(o.getLabel());
        frame.getContentPane().add(sq.getLabel());
        frame.getContentPane().add(tri.getLabel());
    }


}

Mouselistener Class:

import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class clickListener implements MouseListener{

    Squares ob = new Squares();

    public clickListener(Squares newSquare) {
        ob = newSquare;
    }

    public void mouseClicked(MouseEvent e) {
        ob.changePic();
    }

}

And then an object class I'm creating for each image

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class Squares {

    String pic1, pic2, name;
    int x, y, width, height;

    JPanel panel = new JPanel();
    JLabel label = new JLabel();

    public Squares() {
        ;
    }

    public Squares(String pic1, String pic2, int x, int y, int width, int height, String name) {
        this.pic1 = pic1;
        this.pic2 = pic2;
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.name = name;

        BufferedImage myPic1 = null;

        try {
            myPic1 = ImageIO.read(new File(pic1));

        } catch (IOException ex) {System.out.println("error in image upload");}

        /*
        panel.setBounds(x, y, width, height);
        panel.add(new JLabel(new ImageIcon(myPic1)));   
        panel.addMouseListener(new clickListener(this));
        */

        label = new JLabel(new ImageIcon(myPic1));
        label.setBounds(x, y, width, height);
        label.addMouseListener(new clickListener(this));
    }

    public JLabel getLabel() {
        return label;
    }

    public String getName() {
        return this.name;
    }

    public void changePic() {
        JOptionPane.showMessageDialog(null, "change pic reached for " + this.name);
        BufferedImage myPic2 = null;
        try {myPic2 = ImageIO.read(new File(pic2));}
        catch(IOException ex) {System.out.println("error in image upload");}
        label = new JLabel(new ImageIcon(myPic2));
        label.setBounds(x, y, width, height);
        label.repaint();
        label.revalidate();
    }
}

I was originally using JPanels that contained each JLabel, but I got rid of them all to simplify things.

So yeah, any suggestions are appreciated. Thanks!


Solution

  • So the "core" issue is in your changePic method

    public void changePic() {
        JOptionPane.showMessageDialog(null, "change pic reached for " + this.name);
        BufferedImage myPic2 = null;
        try {
            myPic2 = ImageIO.read(new File(pic2));
        } catch (IOException ex) {
            System.out.println("error in image upload");
        }
        label = new JLabel(new ImageIcon(myPic2));
        label.setBounds(x, y, width, height);
        label.repaint();
        label.revalidate();
    }
    

    In this method you create a new instance of JLabel, but it is never added to any container which is attached to the screen, so it can never be shown.

    The simple solution, is to simply change the icon property of the existing instance of JLabel

    public void changePic() {
        JOptionPane.showMessageDialog(null, "change pic reached for " + this.name);
        BufferedImage myPic2 = null;
        try {
            myPic2 = ImageIO.read(new File(pic2));
        } catch (IOException ex) {
            System.out.println("error in image upload");
        }
        label.setIcon(new ImageIcon(pic2));
    }
    

    Observations...

    While going through the code, I found a few things which could be done better.

    • (Despite it's name) It's really not the responsibility of Frame1 to manage the main frame. The core functionality would be better managed through a different container, this decouples the component and makes it more flexible and re-usable in the long wrong - you also don't need to include the complexity of the frame management within the components other responsibilities.
    • You should avoid null layouts. A simpler solution would to be use a GridLayout, as the implementation you've provided has layout issues
    • Squares doesn't need to know the location or size the parent container wants - that's not really a decision the parent container should be making directly. Instead, Squares should be providing sizing hints back to the parent container so it can make better decisions about how to layout all the child components. JLabel is capable of providing this information itself.
    • Arguably Squares should extend from JLabel - this is purely one of simplicity. JLabel is the go to place for displaying images, so it's a good choice for the job, but wrapping a class around it just makes it's management somewhat more cumbersome and in this case, adds very little value. There's an agreement to made for composition over inheritance, but in this case, I'm not sure it would be adding any more value
    • ClickListener doesn't need to create an instance of Squares, it's only constructor requires any caller to pass an instance of Squares to it anyway
    • Because of it's core functionality, Squares should fail if the either of the images can't be loaded, this will make it much easier to diagnose those issues rather then having the program continue running in a "broken" state

    IMHO

    Example

    import java.awt.EventQueue;
    import java.awt.GridLayout;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    import javax.imageio.ImageIO;
    import javax.swing.ImageIcon;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class Game {
    
        public static void main(String[] args) {
            new Game();
        }
    
        public Game() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        try {
                            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                            ex.printStackTrace();
                        }
    
                        JFrame frame = new JFrame("Testing");
                        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                        frame.add(new GamePane());
                        frame.pack();
                        frame.setLocationRelativeTo(null);
                        frame.setVisible(true);
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            });
        }
    
        public class GamePane extends JPanel {
    
            public GamePane() throws IOException {
                initialize();
    
            }
    
            private void initialize() throws IOException {
                Square x = new Square("images\\black-X.png", "images\\blue-X.png", "x");
                Square o = new Square("images\\black-O.png", "images\\blue-O.png", "o");
                Square sq = new Square("images\\black-sq.png", "images\\blue-sq.png", "sq");
                Square tri = new Square("images\\black-tri.png", "images\\blue-tri.png", "tri");
    
                setLayout(new GridLayout(2, 2));
    
                add(x);
                add(o);
                add(sq);
                add(tri);
            }
    
        }
    
        public class ClickListener extends MouseAdapter {
    
            private Square ob;
    
            public ClickListener(Square newSquare) {
                ob = newSquare;
            }
    
            public void mouseClicked(MouseEvent e) {
                ob.changePic();
            }
    
        }
    
        public class Square extends JLabel {
    
            String name;
            private BufferedImage myPic1, myPic2;
    
            public Square(String pic1, String pic2, String name) throws IOException {
                this.name = name;
    
                myPic1 = ImageIO.read(new File(pic1));
                myPic2 = ImageIO.read(new File(pic2));
    
                setIcon(new ImageIcon(myPic1));
                addMouseListener(new ClickListener(this));
            }
    
            public String getName() {
                return this.name;
            }
    
            public void changePic() {
                setIcon(new ImageIcon(myPic2));
            }
        }
    }