Delay when adding JLayerPane to JFrame

75 Views Asked by At

I need to make a hangman game in Java using Swing. I made my hangman figures in photoshop and used the full image as the background layer and then placed the other components on top using JlayeredPane. However, there is always a slight delay when I add the JFrame and open the window. I tried to create a button to add and remove on JLayeredPane when clicked but this caused an even bigger delay. Any assistance will be very appreciated.

public class Game_Screen extends JLayeredPane {
    
    private JLabel hangman;
    
    public Game_Screen() {
        super();
        super.setBounds(0, 0, 1920, 1080);
        
        hangman = new JLabel(new ImageIcon(Game_Screen.class.getResource("/images/hangman10.jpg")));
        hangman.setBounds(0, 0, 1920, 1080);
        
        add(hangman, Integer.valueOf(0));
    }
}

An example of one of the Panes in the game

public class Home_Screen extends Game_Screen {
    
    private JLabel title;
    private JLabel score;
    private JButton start;
    
    public Home_Screen() {
        super();
        title = new JLabel("HANGMAN");
        title.setBounds(490, 170, 942, 217);
        title.setFont(new Font("Press Start", Font.PLAIN, 100));
        add(title, Integer.valueOf(1));
        
        score = new JLabel("High Score: 0");
        score.setBounds(635, 330, 470, 67);
        score.setFont(new Font("Press Start", Font.PLAIN, 30));
        add(score, Integer.valueOf(1));
        
        start = new JButton("START");
        start.setBounds(635,400,400,120);
        start.setBorderPainted(false);
        start.setFont(new Font("Press Start", Font.PLAIN, 60));
        add(start, Integer.valueOf(1));
    }
    
    public void addStartButtonListener(ActionListener a) {
        start.addActionListener(a);
    }
}

JFrame Class

public class HangmanGame extends JFrame {
    
    private Home_Screen home;
    private Level_Screen level;
    private Win_Screen win;
    private Lose_Screen lose;
    private Play_Screen play;
    
    public HangmanGame() {
        super("Hangman");
        
        home = new Home_Screen();
        level = new Level_Screen();
        win = new Win_Screen();
        lose = new Lose_Screen();
        play = new Play_Screen();
        add(home);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(new Dimension(1920,1080));
        setLayout(null);
        setVisible(true);


    }
    
    public Home_Screen getHomeScreen() {
        return home;
    }
    
    public Level_Screen getLevelScreen() {
        return level;
    }
    
    public Win_Screen getWinScreen() {
        return win;
    }
    
    public Lose_Screen getLoseScreen() {
        return lose;
    }
    
    public Play_Screen getPlayScreen() {
        return play;
    }
}

Controller class

public class Controller {
    
    private HangmanGame game;
    
    public Controller() {
        //game = g;
        game = new HangmanGame();
        game.getHomeScreen().addStartButtonListener(new startButtonListener());
    }
    
    class startButtonListener implements ActionListener{

        @Override
        public void actionPerformed(ActionEvent e) {
            game.remove(game.getHomeScreen());
            game.add(game.getLevelScreen());
            
        }
        
    }

}
1

There are 1 best solutions below

3
MadProgrammer On

I'm going to mess with you a little here, hang tight, I'm sure you'll appreciate it.

First, Swing is lazy. When ever you make a change to a component or a layout, you need to tell Swing that you'd like things to be updated.

This means, when you add or remove a component you'd, typically, call revalidate and repaint. Since you're not making a lot of use of layout manager, you could leave revalidate.

However. A simpler solution here would be to make use of a CardLayout. This allows to you to "flip" between views as needed.

One, glaring, issue I have with your approach, is the concept of responsibility. Who is actually responsible for what. You Controller is making a lot of decisions about what should happen, which, if you were using a controller properly might not be a bad idea, but, another idea might be to make each view some what independent and make use of an observer/delegate pattern instead.

For example, you don't really care "how" the Home_Screen starts, only that it can. An observer would be interested in knowing when "start" occurs and take appropriate action.

You've kind of done this with the Controller, but I cringe at the notion that the Controller has a deep understanding of the components, this is just me ;).

Instead, I want things to be as dumb as I can make them, so if I need to change things, it's not a complete re-write each time.

Instead, in the following example, I've made use of a CardLayout on the HangmanGame and supplied a "delegate" to the Home_Screen which tells when "start" occurs (I don't care how, just that it does) and then allowed the HangmanGame to control the navigation.

This is generally a minor thing and you could argue that could use some kind of "navigation controller", but you'd need to do so in away which did expose the implementation details and ... it's a mess ... :P

import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;

public class Controller {

    private HangmanGame game;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Controller();
            }
        });
    }

    public Controller() {
        //game = g;
        game = new HangmanGame();
    }

    public class HangmanGame extends JFrame {

        private Home_Screen home;
        private JPanel level;

        public HangmanGame() {
            super("Hangman");

            CardLayout cardLayout = new CardLayout();

            home = new Home_Screen(new HomeDelegate() {
                @Override
                public void startGame() {
                    cardLayout.show(getContentPane(), "level");
                }
            });
            level = new JPanel();
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            setSize(new Dimension(1920, 1080));
            setLayout(cardLayout);
            add(home, "home");
            add(level, "level");
            setVisible(true);

        }
    }

    public interface HomeDelegate {

        public void startGame();
    }

    public class Home_Screen extends Game_Screen {

        private JLabel title;
        private JLabel score;
        private JButton start;

        public Home_Screen(HomeDelegate delegate) {
            super();
            title = new JLabel("HANGMAN");
            title.setBounds(490, 170, 942, 217);
            title.setFont(new Font("Press Start", Font.PLAIN, 100));
            add(title, Integer.valueOf(1));

            score = new JLabel("High Score: 0");
            score.setBounds(635, 330, 470, 67);
            score.setFont(new Font("Press Start", Font.PLAIN, 30));
            add(score, Integer.valueOf(1));

            start = new JButton("START");
            start.setBounds(635, 400, 400, 120);
            start.setBorderPainted(false);
            start.setFont(new Font("Press Start", Font.PLAIN, 60));
            add(start, Integer.valueOf(1));

            start.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent arg0) {
                    delegate.startGame();
                }
            });
        }
    }

    public class Game_Screen extends JLayeredPane {

        private JLabel hangman;

        public Game_Screen() {
            super();

            hangman = new JLabel(new ImageIcon(getClass().getResource("/images/hangman10.png")));
            hangman.setBounds(0, 0, 1920, 1080);

            add(hangman, Integer.valueOf(0));
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(1920, 1080);
        }
    }
}

Now, the initial load delay is expected of Swing, as it can take a few seconds for the Event Dispatching Thread to spin up and all the required initialisation to complete, but one part "might" be the loading of the image, although, before I started spending a lot of time worrying about, I'd focus on just making your game work