Search code examples
javaswingpuzzle

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


I only learn Java Swing 1 week so I tried complete some exercises. This is my code. I set 9 icon to 9 button but it doesn't show on button.

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

I think method setIcon is not apply for Button. Besides, someone show me how to set a action to arrange mess picture into a complete picture in Puzzle Game with my code.


Solution

  • I see some issues in your code:

    1. You're extending JFrame and creating a new JFrame object inside your class. You're never using the JFrame of your class (the extended one). So it's wise to just remove it.

      You should avoid extending JFrame because that means that your class is a JFrame, JFrame is a rigid container, instead make your programs based on JPanels and add them to other Containers. For reference see: Using extends JFrame vs calling it inside of class.

    2. You're over using the static keyword. static is not a cross method passing word, it will harm you a lot, stop using it. Instead create instances of your class and call your methods that way.

    3. You have multiple objects that do the same:

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

      Why not have an Icon[] icons and iterate over it?

    4. You're using the evil null layout and setBounds(...), stop using it and instead make use of the layout managers along with EmptyBorders for extra spacing between components.

      While pixel perfect positioning might be like the easiest way to Swing newbies to create complex GUIs, the more you use it, the more problems you'll find regarding this. Swing has to deal with different platforms, screen sizes, resolutions, PLAFs, etc. That's why pixel perfect GUIs are just an illusion. For reference see Null layout is evil and the answers in this question for a further explanation on why you should avoid null layout.

    5. You're making your JFrame visible before you have added all your components, this could cause your GUI to not be painted fully before it's shown and could cause a "bug" that the components don't display until you hover where they should be. JFrame#setVisible(...) should be one of the last lines to be called.

    6. You're calling 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? (The general consensus says "yes").

    7. You're not placing your program on the Event Dispatch Thread (EDT), Swing is not Thread safe and this could make your program to freeze sometimes, you can solve this by changing your main(...) method like this one:

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

    Your images probably aren't located, but they'll become embedded resources once you package your program as a JAR file, and thus, it's wise to start treating the files (or images) as if they already were.

    You can change for example:

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

    To this:

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

    That will make your image to be loaded. See this question and the accepted answer for reference.

    That should solve your question, and your GUI (I did it with 2 icons) shoudl look like this one:

    enter image description here

    But if you want to follow my above recommendations you could try this code, which uses layout managers, empty borders, overrides getPreferredSize() methods and uses pack(), etc and generates a really similar GUI like the one you already have:

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

    enter image description here

    As you can see, the code is at most 20 lines longer than the one you already have, but if you keep adding elements to both programs, the one I did will, in a future, be shorter than the one you'll end up by using null layout.

    I hope you follow the above recommendations and take this example and try to understand it and improve it.