Java Swing - Components Not Visible Unless in Constructor?

70 Views Asked by At

I'm making a Brick Breaker type game in Java using Swing, but none of my Jcomponents are visible unless they are created in the constructor. Another weird note is my current components are only visible if I run setVisible() immediately after adding it to the panel. How can I be able to add them later via methods (e.g. within startGame())?

Main.java

public class Main {
    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                BrickBreaker gameInstance = new BrickBreaker();
                gameInstance.startGame();
            }
        });
    }
}

BrickBreaker.java

import java.awt.BorderLayout;
import java.awt.Color;
import java.net.URL;
import java.util.ArrayList;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class BrickBreaker {

    boolean gameOver = false;
    int frameBoundX = 500;
    int frameBoundY = 600;
    JFrame frame;
    JPanel panel;
    Ball ball;
    Slider slider;
    ArrayList<Brick> brickList = new ArrayList<Brick>();

    public BrickBreaker() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(frameBoundX, frameBoundY);
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);

        // Add new panel
        panel = new JPanel(new BorderLayout());
        frame.setContentPane(panel);

        // Add slider
        slider = new Slider(200, frameBoundY - 100, 100, 30);
        panel.add(slider);
        frame.setVisible(true);

        // Add ball
        ball = new Ball(this, frameBoundX, frameBoundY);
        panel.add(ball);
        frame.setVisible(true);

        // Add Bricks
        generateBricks();
        for (Brick brick : brickList) {
            panel.add(brick);
            frame.setVisible(true);
        }

        // Add background
        URL backgroundUrl = BrickBreaker.class.getResource("background.jpg");
        ImageIcon backgroundIcon = new ImageIcon(backgroundUrl);
        JLabel backgroundLabel = new JLabel(backgroundIcon);
        backgroundLabel.setBounds(0, 0, backgroundIcon.getIconWidth(),
                backgroundIcon.getIconHeight());
        panel.add(backgroundLabel);
        frame.setVisible(true);
    }

    public void startGame() {
        // Add ball
        // ball = new Ball(this, frameBoundX, frameBoundY);
        // panel.add(ball);
        // setVisible(true);
        // panel.repaint();
        // panel.revalidate();

        slider.start();
        ball.start();
    }

    public void endGame() {
        System.out.println("GAME OVER");
        // JLabel endSign = new JLabel("MY TEXT HERE");
        // endSign.setText("GAME OVER");
        // endSign.setBounds(frameBoundX/2, frameBoundY/2, 200, 200);
        // endSign.setForeground(Color.white);
        // endSign.setVisible(true);
        // panel.add(endSign);
        // panel.setVisible(true);
        // // Repaint the panel
        // panel.revalidate();
        // panel.repaint();
        slider.end();
    }

    public boolean isGameOver() {
        return gameOver;
    }

    public void generateBricks() {
        // Unique starting and iterating numbers are due to the window size
        // while also spacing out the bricks evenly
        for (int row = 10; row <= 85; row += 35) {
            for (int b = 5; b < frameBoundX; b += 99) {
                System.out.println("Generating brick #" + b);
                Brick brick = new Brick(b, row);
                brickList.add(brick);
            }
        }
    }
}

Brick.java (to see how components are implemented)

import javax.swing.JComponent;
import java.awt.Rectangle;
import java.awt.Color;
import java.awt.Graphics;

public class Brick extends JComponent {

    private int x;
    private int y;
    private int width = 94;
    private int height = 30;
    private int health = 1;

    public Brick(int startingX, int startingY) {
        this.x = startingX;
        this.y = startingY;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.MAGENTA);
        g.fillRect(x, y, width, height);
        g.dispose();
    }

    public Rectangle getBounds() {
        return new Rectangle(x, y, width, height);
    }

    public int reduceHealth(){
        this.health--;
        if (this.health < 1){
            System.out.println("Brick has zero health! Disappearing!");
            setVisible(false);
        }
        return this.health;
    }

}

If I try to declare the Slider, Ball, or Bricks anywhere else outside of the constructor they won't appear on screen. Originally, I also had the Main java file utilizing EDT by calling invokeLater() but I removed that thinking that might've been an issue, but it's still happening.

1

There are 1 best solutions below

0
Reilas On

"... none of my Jcomponents are visible unless they are created in the constructor. ... are only visible if I run setVisible() immediately after adding it to the panel. How can I be able to add them later via methods (e.g. within startGame())? ..."

Modularize the data.
Create a method or class, for each task.

I recommend encapsulating the GUI related data.
Additionally, since Ball is accessing this, frameBoundX, and frameBoundY, just nest it within that class.

Here is an example.

class BrickBreaker {

    GUI gui;
    boolean gameOver = false;

    static class GUI extends JFrame {
        int frameBoundX, frameBoundY;
        JPanel panel;
        Ball ball;
        Slider slider;
        ArrayList<Brick> brickList;

        GUI() {
            initialize();
            // Add new panel
            add(panel);
            // Add slider
            panel.add(slider);
            // Add ball
            panel.add(ball);
            // Add Bricks
            for (Brick b : brickList) panel.add(b);
            // Add background
            URL backgroundUrl = BrickBreaker.class.getResource("background.jpg");
            ImageIcon backgroundIcon = new ImageIcon(backgroundUrl);
            JLabel backgroundLabel = new JLabel(backgroundIcon);
            backgroundLabel.setBounds(0, 0, backgroundIcon.getIconWidth(),
                backgroundIcon.getIconHeight());
            panel.add(backgroundLabel);
            panel.setVisible(true);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setSize(frameBoundX, frameBoundY);
            setLocationRelativeTo(null);
            setResizable(false);
            setVisible(true);
        }

        void initialize() {
            frameBoundX = 500;
            frameBoundY = 600;
            panel = new JPanel(new BorderLayout());
            ball = new Ball();
            slider = new Slider(200, frameBoundY - 100, 100, 30);
            generateBricks();
        }

        public void generateBricks() { ... }

        private class Ball extends JComponent {
        }

        private class Slider extends JComponent {
            public Slider(int x, int y, int w, int h) {
            }
        }
    }

    public void startGame() { ... }

    public void endGame() { ... }

    public boolean isGameOver() { ... }
}
SwingUtilities.invokeLater(() -> {
    BrickBreaker gameInstance = new BrickBreaker();
    gameInstance.gui = new BrickBreaker.GUI();
    gameInstance.startGame();
    while (!gameInstance.isGameOver()) {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    gameInstance.endGame();
});