单击图像时 JFrame 图像更新

JFrame Image Update on click of image

tldr; 如何在 JFrame 对象(特别是 JLabel)上使用 MouseEvent 来更新 JFrame 中显示的图像

我正在尝试创建一个程序,将图像分解成图块,然后单击其中一个图块,程序会将图块移动到图像中打开的 space。 (有关详细信息,请参阅 Sliding Puzzle)。

我目前能够 select 图像,将图像分解成图块,然后“随机”删除其中一个图块。

我的下一步是,单击其中一个图块,将其与空图块交换(我将在稍后处理要交换的图块的“资格”,但现在,只是希望能够将 selected 的图块与当前的空白图块交换)

为了创建 3x3 网格的图像,我将 bufferedImage 分成 9 个相等的部分,“随机”空白其中一个图像,然后使用 GridLayout 在 jLabels 中显示图像以将它们排列起来。

当我将 mouseListeners 添加到每个 jLabel 时,我可以看到我正在进入 MouseListener,因为我可以看到如下所示的控制台日志消息“已单击”,但是,我无法根据需要更新图像.

如何在这些 JFrame JLabel 上使用 MouseEvent 来调用 ImageContainer 中的方法(即 move())并更新显示的图像?

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

创建自定义 JLabel class 以保持位置 (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 来实例化这个 class 而不是 JLabels 并且在 mouseListener 中你可以调用 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();
            }
        });
    }

我的建议是不要在 JFrame 对象上调用 .add 和 .removeAll,而是使用 GridLayout 创建一个新的 JPanel 并使用 jframe.getContentPanel().add(labelsPanel)。如果它没有刷新,您可以在 JPanel 上调用 revalidate()。

不要remove/add组件。而只是交换图标。

  1. 将 JPanel 与在每个网格中包含一个 JLabel 的 GridLayout 结合使用
  2. 为每个 JLabel 添加一个 ImageIcon(一个除外)
  3. 为每个 JLabel 添加一个 MouseListner。
  4. 在 mouseClicked 事件中,您获取被单击的标签并删除 ImageIcon 并将图标添加到空标签。

因此,当您创建电路板时,您可以有一个像 emptyLabel 这样的变量,它会被初始化为没有图标的标签。那么 mouseClicked 中的逻辑可能是这样的:

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

此答案基于 this 之前的答案,并根据您的代码进行了调整。
它是一个 one-file mre : 整个代码可以是 copy-pasted 到 ImageJumble.java 文件,并且 运行。
它支持从任何网格位置到任何其他位置的 DnD。您可能想要更改它。
请注意评论:

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);
    }
}