使用 Java 将 Image 设置为 Button 并在 Puzzle Game 中处理 ActionListener

Set Image to Button and process ActionListener in Puzzle Game using Java

我只学习 Java Swing 1 周,所以我尝试完成一些练习。这是我的代码。我将 9 图标设置为 9 按钮,但它没有显示在按钮上。

package mypack;

import java.awt.Color;

import javax.swing.*;

public class PuzzleGame extends JFrame{
    static JButton bt1,bt2,bt3,bt4,bt5,bt6,bt7,bt8,bt9,btNew,btExit;
    static JLabel move, moveNum, time, timeNum;
    public PuzzleGame(){
        createMyGUI();
    }

    public static void createMyGUI(){
        JFrame jf = new JFrame("Game Puzzle Java");
        JPanel jpl = new JPanel();
        Icon icSpace = new ImageIcon("images/0.png");
        Icon ic1 = new ImageIcon("images/1.png");
        Icon ic2 = new ImageIcon("images/2.png");
        Icon ic3 = new ImageIcon("images/3.png");
        Icon ic4 = new ImageIcon("images/4.png");
        Icon ic5 = new ImageIcon("images/5.png");
        Icon ic6 = new ImageIcon("images/6.png");
        Icon ic7 = new ImageIcon("images/7.png");
        Icon ic8 = new ImageIcon("images/8.png");
        jpl.setSize(100,100);
        jpl.setBounds(480, 50, 200, 200);
        jpl.setBackground(Color.BLUE);
        move = new JLabel("Move:");
        move.setBounds(480,10,50,20);
        moveNum = new JLabel("0");
        moveNum.setBounds(530, 10, 50, 20);
        time = new JLabel("Time:");
        time.setBounds(580, 10, 50, 20);
        timeNum = new JLabel("0");
        timeNum.setBounds(630,10,50,20);
        btNew = new JButton("New Game");
        btNew.setBounds(480, 270, 200, 80);
        btExit = new JButton("Exit");
        btExit.setBounds(480, 370, 200, 80);
        jf.add(move);
        jf.add(moveNum);
        jf.add(time);
        jf.add(timeNum);
        jf.add(btNew);
        jf.add(btExit);
        jf.add(jpl);
        jf.setSize(700, 500);
        jf.setLocation(300,20);
        jf.setLayout(null);
        jf.setResizable(false);
        jf.setVisible(true);
        bt1 = new JButton();
        bt1.setBounds(10, 10, 150, 150);
        bt1.setIcon(ic1);
        bt2 = new JButton();
        bt2.setBounds(160, 10, 150, 150);
        bt2.setIcon(ic2);
        bt3 = new JButton();
        bt3.setBounds(310, 10, 150, 150);
        bt3.setIcon(ic3);
        bt4 = new JButton();
        bt4.setBounds(10, 160, 150, 150);
        bt4.setIcon(ic4);
        bt5 = new JButton();
        bt5.setBounds(160, 160, 150, 150);
        bt5.setIcon(ic5);
        bt6 = new JButton();
        bt6.setBounds(310, 160, 150, 150);
        bt6.setIcon(ic6);
        bt7 = new JButton();
        bt7.setBounds(10, 310, 150, 150);
        bt7.setIcon(ic7);
        bt8 = new JButton();
        bt8.setBounds(160, 310, 150, 150);
        bt8.setIcon(ic8);
        bt9 = new JButton();
        bt9.setBounds(310, 310, 150, 150);
        bt9.setIcon(icSpace);
        jf.add(bt1);
        jf.add(bt2);
        jf.add(bt3);
        jf.add(bt4);
        jf.add(bt5);
        jf.add(bt6);
        jf.add(bt7);
        jf.add(bt8);
        jf.add(bt9);
    }

    public static void main(String[] args){
        PuzzleGame.createMyGUI();
    }   
}

我认为 setIcon 方法不适用于 Button。另外,有人用我的代码告诉我如何在Puzzle Game中设置一个动作,将乱七八糟的图片整理成完整的图片。

我发现您的代码中存在一些问题:

  1. 您正在扩展 JFrame 并在 class 中创建一个新的 JFrame 对象。您永远不会使用 class(扩展的)的 JFrame。所以删除它是明智的。

    您应该避免扩展 JFrame,因为这意味着您的 class JFrameJFrame 是刚性的container,而不是让你的程序基于 JPanels 并将它们添加到其他 Container。参考:Using extends JFrame vs calling it inside of class.

  2. 您已结束使用 static 关键字。 static不是交叉传话法,对你的伤害很大,别用了。而是创建 class 的实例并以这种方式调用您的方法。

  3. 您有多个执行相同操作的对象:

    Icon ic1 = new ImageIcon("images/1.png");
    Icon ic2 = new ImageIcon("images/2.png");
    ...
    

    为什么不用 Icon[] icons 并对其进行迭代?

  4. 您正在使用邪恶的 null layoutsetBounds(...),停止使用它并使用 layout managers along with EmptyBorder 来增加组件之间的额外间距。

    虽然像素完美定位可能是 Swing 新手创建复杂 GUI 的最简单方法,但您使用它的次数越多,您就会发现与此相关的问题越多。 Swing 必须处理不同的平台、屏幕尺寸、分辨率、PLAF 等。这就是为什么像素完美的 GUI 只是一种幻觉。作为参考,请参阅 Null layout is evil and the answers in this question 进一步解释为什么你应该避免 null layout.

  5. 您在添加所有组件之前让 JFrame 可见,这可能会导致您的 GUI 在显示之前无法完全绘制并可能导致 "bug" 在您将鼠标悬停在它们应该出现的位置之前,组件不会显示。 JFrame#setVisible(...) 应该是最后调用的行之一。

  6. 您正在调用 JFrame#setSize(...), you should instead override the getPreferredSize of your inner JPanels and then call JFrame#pack(), so your JFrame reduces its size to the minimum size where all your components are visible on their preferred sizes. See Should I avoid the use of setPreferred|Maximum|MinimumSize in Java Swing?(普遍共识是 "yes")。

  7. 您没有将您的程序放在 Event Dispatch Thread (EDT) 上,Swing 不是线程安全的,这有时会使您的程序冻结,您可以通过更改 main(...) 像这样的方法:

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                //Your constructor here
            }
        });
    }
    

您的图像可能未找到,但一旦您将程序打包为 JAR 文件,它们就会成为嵌入式资源,因此,明智的做法是开始将文件(或图像)视为已经存在。

您可以更改例如:

Icon ic1 = new ImageIcon("images/1.png");

为此:

Icon ic1 = null;
try {
    ic1 = new ImageIcon(ImageIO.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("images/1.png")));
} catch (IOException e) {
    e.printStackTrace();
}

这将使您的图片加载。请参阅此 question 和已接受的答案以供参考。

这应该可以解决您的问题,您的 GUI(我用 2 个图标完成的)应该如下所示:

但是如果你想遵循我上面的建议,你可以试试这个代码,它使用布局管理器、空边框、覆盖 getPreferredSize() 方法并使用 pack() 等,并生成一个非常相似的 GUI喜欢你已经拥有的那个:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class ImagesInResourcesExample {

    private JFrame frame;

    private JPanel buttonsPane;
    private JPanel rightPane;
    private JPanel scorePanel;
    private JPanel colorPanel;

    private BufferedImage img;

    private JButton[][] buttons;

    private JLabel moveLabel;
    private JLabel timeLabel;

    private JButton newGameButton;
    private JButton exitButton;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new ImagesInResourcesExample().createAndShowGui();
            }
        });
    }

    @SuppressWarnings("serial")
    public void createAndShowGui() {
        frame = new JFrame(getClass().getSimpleName());
        buttons = new JButton[3][3];
        moveLabel = new JLabel("Move: 0");
        timeLabel = new JLabel("Time: 0");
        colorPanel = new JPanel() {
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(200, 200);
            }
        };
        colorPanel.setBackground(Color.BLUE);
        colorPanel.setOpaque(true);

        newGameButton = new JButton("New Game");
        exitButton = new JButton("Exit");

        try {
            img = ImageIO.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("images/arrow.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        buttonsPane = new JPanel() {
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(500, 500);
            }
        };
        buttonsPane.setLayout(new GridLayout(3, 3));
        buttonsPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

        for (int i = 0; i < buttons.length; i++) {
            for (int j = 0; j < buttons.length; j++) {
                buttons[i][j] = new JButton(new ImageIcon(img));
                buttonsPane.add(buttons[i][j]);
            }
        }

        rightPane = new JPanel();
        rightPane.setLayout(new GridBagLayout());

        scorePanel = new JPanel();
        scorePanel.setLayout(new GridLayout(1, 2, 10, 10));

        scorePanel.add(moveLabel);
        scorePanel.add(timeLabel);

        GridBagConstraints gbc = new GridBagConstraints();

        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.fill = GridBagConstraints.BOTH;
        gbc.insets = new Insets(10, 10, 10, 10);

        rightPane.add(scorePanel, gbc);

        gbc.gridx = 0;
        gbc.gridy = 1;

        rightPane.add(colorPanel, gbc);

        gbc.gridy = 2;
        gbc.ipadx = 30;
        gbc.ipady = 80;

        rightPane.add(newGameButton, gbc);

        gbc.gridy = 3;

        rightPane.add(exitButton, gbc);

        rightPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

        frame.add(buttonsPane, BorderLayout.CENTER);
        frame.add(rightPane, BorderLayout.EAST);
        frame.pack();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

如您所见,代码最多比您已有的代码多 20 行,但是如果您继续向两个程序中添加元素,那么将来我做的代码会比现在的代码短你最终会使用 null layout.

希望大家按照上面的建议,以这个例子来理解和改进。