JFrame Image Update on click of image

332 views Asked by At

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 3x3 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;
    }
}

4

There are 4 answers

0
camickr On BEST 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;
0
Conffusion On

Create a custom JLabel class to hold the position (i):

class MyImageLabel extends JLabel {
    private int position;
    public MyImageLabel(Icon image,int position) {
        super(image);
        this.position=position;
    }
    public int getPosition()
    {
        return position;
    }
}

Adapt createImage to instantiate this class instead of JLabels and in the mouseListener you can call getPosition():

    for(int i = 0; i < jLabels.length; i++) {
        MyImageLabel jLabel = new MyImageLabel(new ImageIcon(imageContainer.getBufferedImages().get(i)),i);
        jFrame.add(jLabel);
        jLabel.addMouseListener(new MouseAdapter()
        {
            public void mouseClicked(MouseEvent e)
            {
                System.out.println("Clicked!");
                imageContainer.move(jLabel.getPosition());
                jFrame.removeAll();
                createImage();
            }
        });
    }
0
Anthony On

My advice is not to call .add and .removeAll on the JFrame object but create a new JPanel with the GridLayout and use jframe.getContentPanel().add(labelsPanel). If it doesn't refresh you can then call revalidate() on your JPanel.

0
c0der On

This answer is based on this previous answer, adapted to your code.
It is a one-file mre : the entire code can be copy-pasted to ImageJumble.java file, and run.
It supports DnD from any grid location to any other. You may want to change it.
Please note the comments:

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class ImageJumble extends JPanel {

    private static int difficulty = 3;
    private static final String FLOWER = "http://www.digitalphotoartistry.com/rose1.jpg";
    private static final int GAP = 4;

    private JPanel content;

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

    public static void main(String[] args) {
        new ImageJumble().run();
    }

    private void displayImage() {

        BufferedImage bi = null;

        try {
            bi = ImageIO.read(new URL(FLOWER));
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        content = new JPanel(new GridLayout(difficulty,difficulty, GAP,GAP));
        createImage(bi);
        JFrame jFrame = new JFrame("Image Jumble");
        jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        jFrame.add(content);
        jFrame.pack();
        jFrame.setVisible(true);
    }

    private void createImage( BufferedImage bi) {

        ImageContainer imageContainer = new ImageContainer(bi,difficulty);
        imageContainer.randomize();

        for(int i = 0; i < difficulty * difficulty; i++) {
            content.add(new DragDropPane(imageContainer.getBufferedImages().get(i)));;
        }
    }
}

class ImageContainer {
    private final 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 randomize() {
        Collections.shuffle(bufferedImages);//shuffle images
        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));
    }
}

class DragDropPane extends JPanel implements DragGestureListener, DragSourceListener {

    private JComponent dragable;
    private final BufferedImage bi;

    public DragDropPane(BufferedImage bi) {

        setBackground(Color.BLACK);
        this.bi = bi;
        var label = new JLabel(new ImageIcon(bi), JLabel.CENTER);
        setContent(label);
        new MyDropTargetListener(this);
        DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(
                                        this, DnDConstants.ACTION_COPY, this);
    }

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

    public void setContent(JComponent component) {
        removeAll();
        dragable = component;
        add(component);
        repaint();
    }

    //-->DragGestureListener implementation

    @Override
    public void dragGestureRecognized(DragGestureEvent dge) {

        // Create our transferable wrapper
        Transferable transferable = new TransferableComponent(dragable);

        // Start the "drag" process...
        DragSource ds = dge.getDragSource();
        ds.startDrag(dge, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR), transferable, this);
        remove(dragable);
        revalidate(); repaint();
    }

    //-->DragSourceListener implementation

    @Override
    public void dragEnter(DragSourceDragEvent dsde) {}

    @Override
    public void dragOver(DragSourceDragEvent dsde) {}

    @Override
    public void dropActionChanged(DragSourceDragEvent dsde) {}


    @Override
    public void dragExit(DragSourceEvent dse) {}

    @Override
    public void dragDropEnd(DragSourceDropEvent dsde) {
        // If the drop was not successful, we need to
        // return the component back to it's previous
        // parent
        if (!dsde.getDropSuccess()) {
            setContent(dragable);
        }
    }
}

class MyDropTargetListener extends DropTargetAdapter {

    private final DragDropPane target;

    public MyDropTargetListener(DragDropPane target) {
        this.target = target;
        new DropTarget(target, DnDConstants.ACTION_COPY, this, true, null);
    }

    @Override
    public void drop(DropTargetDropEvent event) {

        try {

            var tr = event.getTransferable();
            var component = (JComponent) tr.getTransferData(TransferableComponent.component);

            if (event.isDataFlavorSupported(TransferableComponent.component)) {

                event.acceptDrop(DnDConstants.ACTION_COPY);
                target.setContent(component);
                event.dropComplete(true);

            } else {
                event.rejectDrop();
            }

        } catch (Exception e) {
            e.printStackTrace();
            event.rejectDrop();
        }
    }
}

class TransferableComponent implements Transferable {

    protected static final DataFlavor component =
            new DataFlavor(JComponent.class, "A Component");

    protected static final DataFlavor[] supportedFlavors = {
            component
    };

    private final JComponent componentToTransfer;

    public TransferableComponent(JComponent componentToTransfer) {

        this.componentToTransfer = componentToTransfer;
    }

    @Override
    public DataFlavor[] getTransferDataFlavors() {

        return supportedFlavors;
    }

    @Override
    public boolean isDataFlavorSupported(DataFlavor flavor) {

        return flavor.equals(component);
    }

    @Override
    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {

        if (flavor.equals(component)) return componentToTransfer;
        else  throw new UnsupportedFlavorException(flavor);
    }
}

enter image description here