JFrame Image Update on click of image

Tags: , , ,



tldr; How do you use a MouseEvent on a JFrame object(specifically JLabel) to update the displayed image in the JFrame

I am trying to create a program where an image is broken into tiles and on click of one of those tiles, the program moves the tile to the open space in the image. (See Sliding Puzzle for more information).

I currently am able to select an image, break the image into tiles, and “randomly” remove one of the tiles.

My next step would be, on click of one of the tiles, to swap it with the empty tile (I will work on the “eligibility” of tiles to be swapped at a later time, but for now, just want to be able to swap the tile selected with the current blank tile)

To create my image for a 3×3 grid, I split a bufferedImage into 9 equal pieces, “randomly” blank one of the images, and then display the images in jLabels using GridLayout to line them up.

When I add mouseListeners to each jLabel, I am able to see that I am entering the MouseListener as I can see the console log message “Clicked” as shown below, however, am not able to update the image as desired.

How can I use a MouseEvent on these JFrame JLabels to call a method within ImageContainer(i.e. move()) and update the displayed image?

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;

public class ImageJumble extends JPanel {

    static int difficulty = 0;

    ImageContainer imageContainer;
    JFrame jFrame = new JFrame("Image Jumble");

    private void run() {
        SwingUtilities.invokeLater(this::displayImage);
    }

    public static void main(String[] args) {
        System.out.print("Please enter the difficulty level (1-3): ");
        Scanner scanner = new Scanner(System.in);
        difficulty = scanner.nextInt();

        new ImageJumble().run();
    }

    private void displayImage() {
        JFileChooser fc = new JFileChooser();
        fc.setDialogTitle("Please choose an image...");
        FileNameExtensionFilter filter = new FileNameExtensionFilter("JPEG", "jpeg", "jpg", "png", "bmp", "gif");
        fc.addChoosableFileFilter(filter);

        BufferedImage image = null;

        if (fc.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
            File selectedFile = fc.getSelectedFile();
            try {
                image = ImageIO.read(selectedFile);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }


        jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        jFrame.setSize(image.getWidth(), image.getHeight());
        jFrame.setVisible(true);
        jFrame.setLayout(new GridLayout(difficulty,difficulty,0,0));


        imageContainer = new ImageContainer(image,difficulty);

        createImage();
    }

    private void createImage() {
        imageContainer.randomize();
        JLabel[] jLabels = new JLabel[difficulty * difficulty];
        for(int i = 0; i < jLabels.length; i++) {
            JLabel jLabel = new JLabel(new ImageIcon(imageContainer.getBufferedImages().get(i)));
            jFrame.add(jLabel);
            jLabel.addMouseListener(new MouseAdapter()
            {
                public void mouseClicked(MouseEvent e)
                {
                    System.out.println("Clicked!");
                    imageContainer.move(i);
                    jFrame.removeAll();
                    createImage();
                }
            });
        }
    }


}


import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class ImageContainer {
    private List<BufferedImage> bufferedImages = new ArrayList<>();
    private int blankLocation = 0;

    public ImageContainer(BufferedImage image, int difficulty) {
        BufferedImage bi = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
        Graphics g = bi.createGraphics();
        g.drawImage(image, 0, 0, null);
        int width = bi.getWidth();
        int height = bi.getHeight();
        int swidth = width / difficulty;
        int sheight = height / difficulty;

        for (int i = 0; i < difficulty; i++) {
            for (int j = 0; j < difficulty; j++) {
                BufferedImage bimg = bi.getSubimage(j * swidth, i * sheight, swidth, sheight);
                bufferedImages.add(bimg);
            }
        }


    }


    public List<BufferedImage> getBufferedImages() {
        return bufferedImages;
    }

    public void setBufferedImages(List<BufferedImage> bufferedImages) {
        this.bufferedImages = bufferedImages;
    }

    public void randomize() {
        int size = bufferedImages.size();
        int width = bufferedImages.get(0).getWidth();
        int height = bufferedImages.get(0).getHeight();
        blankLocation = new Random().nextInt(size);
        bufferedImages.set(blankLocation, new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB));
    }

    public void move(int i) {
        bufferedImages.set(blankLocation,bufferedImages.get(i));
        blankLocation = i;
    }
}

Answer

Don’t remove/add components. Instead just swap the Icon.

  1. Use a JPanel with a GridLayout that contains a JLabel in each grid
  2. Add an ImageIcon to each JLabel (except for one)
  3. Add a MouseListner to each JLabel.
  4. In the mouseClicked event you get the label that was clicked and remove the ImageIcon and add the Icon to the empty label.

So when you create the board you could have a variable like emptyLabel which would be initialized to the label without the Icon. Then the logic in the mouseClicked might be something like:

JLabel clicked = (JLabel)e.getSource();
emptyLabel.setIcon( clicked.getIcon() );
emptyLabel = clicked;


Source: stackoverflow