Search code examples
javaswingnullpointerexceptionjpanelgraphics2d

how to add JPanels on JPanels while changing the background and making them visible


I'm trying to make a tic-tac-toe game with A.I in java, but I'm new to Swing and graphics with Java. First off, I can't change the background color of my JPanel even when I set Opaque true(see class ttt below). I was however able to change the background of the JFrame (see class tttFrame). Secondly, when I try to add JPanels (these panels are where the X and O's will go) to the top of my current JPanel - the one with the lines (or maybe it does, but the background color of the JPanels don't change) they won't show. The testpanel and the panels in tttGrid both don't show up. I'm assuming I got the wrong idea of how to implement JFrames and JPanels.

My code also throws a nullpointerexception at this.add(tttGrid[r][c]);. tttGrid is basically a 2D array of JPanels that are intended to display X or O when clicked by the user/computer. I'm not sure how my code leads to the nullpointerexception. I did some testing as you'd notice on the lines before this.add(tttGrid[r][c]);, I get this output on the console window:

actual label, row 0 col 0
actual label, row 0 col 1
actual label, row 0 col 2
null label, row 1 col 0
null label, row 1 col 1
null label, row 1 col 2
null label, row 2 col 0
null label, row 2 col 1
null label, row 2 col 2
import java.awt.*;
import java.util.*;
import javax.swing.*;


public class ttt extends JPanel{
    
    private char playerChoice;
    private JPanel[][] tttGrid;

    public ttt(){
        
        //this.setOpaque(true);             Why isn't setBackground working in JPanel and 
        //this.setBackground(Color.BLUE);   only working in JFrame class?
        this.setPreferredSize(new Dimension(500,500));
        this.setFocusable(true);

        tttGrid = new JPanel[3][3];
        for(int y = 50; y <= 290; y+=120){
            int r = 0;
            int c = 0;
            for(int x = 70; x <= 310; x+=120){
                tttGrid[r][c] = new JPanel();   
                tttGrid[r][c].setBounds(x,y,120,120);
                tttGrid[r][c].setBackground(Color.GREEN);
                tttGrid[r][c].setOpaque(true);
                c++;
            }
            r++;
        }
        
        for(int r = 0; r < 3; r++){
            for(int c = 0; c < 3; c++){
                if(tttGrid[r][c] == null){
                    System.out.println("null panel, row " + r + " col " + c);
                }
                else{
                    System.out.println("real panel, row " + r + " col " + c);
                }
                this.add(tttGrid[r][c]); //nullpointerexception
            }
        }
        
        //testing if JPanel is visible 
        JPanel testpanel = new JPanel();
        testpanel.setBounds(70,50,120,120);
        testpanel.setBackground(Color.MAGENTA);
        testpanel.setOpaque(true);
        this.add(testpanel);
        
    }


    public void paint(Graphics g){
        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(new Color(153, 250, 255));
        g2d.setStroke(new BasicStroke(5));
        g2d.drawLine(70, 170, 430, 170);
        g2d.drawLine(70, 290, 430, 290);
        g2d.drawLine(190, 50, 190, 410);
        g2d.drawLine(310, 50, 310, 410);
    }

    public void gameStart(){

    }

    public void drawXO(){

    }

}

import javax.swing.*;
import java.awt.*;

public class tttFrame extends JFrame{
    
    public tttFrame(){

        this.getContentPane().add(new ttt());
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setTitle("Tic-Tac-Toe");
        ImageIcon icon = new ImageIcon("ttt.png");
        this.setIconImage(icon.getImage());
        this.setBackground(new Color(0,225,237)); //Background color only works on JFrame
        this.setResizable(false);
        this.pack();
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    public static void main(String[] args) {
        new tttFrame();
  }
}

The photo below is what I get when I comment out the code this.add(tttGrid[r][c]);

output when code is run


Solution

  • So, several compounding issues.

    First, your NPE is been caused by the fact you are resetting the r value on each iteration of your compounded for-loops

    for (int y = 50; y <= 290; y += 120) {
        int r = 0;
        int c = 0;
        for (int x = 70; x <= 310; x += 120) {
            tttGrid[r][c] = new JPanel();
            tttGrid[r][c].setBounds(x, y, 120, 120);
            tttGrid[r][c].setBackground(Color.GREEN);
            tttGrid[r][c].setOpaque(true);
            c++;
        }
        r++;
    }
    

    In this case, r is ALWAYS 0

    Next, you're fighting the layout manager of the panel. A JPanel by default makes use of a FlowLayout. Setting the bounds of a component in this case will have no effect and the FlowLayout manager will attempt to make use of the components preferredSize to determine how best it should be laid out.

    This would have produced an undesirable result for you, expect ...

    Next, you're fighting the paint subsystem.

    By overriding paint you are taking over complete control of how the component, and its children, are painted. If you don't want that much control, you should first call super.paint to ensure that the paint chain is maintained.

    However, as a general rule, you should really consider overriding paintComponent instead (just don't forget to call super.paintComponent first)

    Take a look at:

    for more details.

    Runnable example

    The following example makes use of GridBagLayout. This is a some what more complicated layout manager and you could get a similar result using a GridLayout, but I like the flexibility that GridBagLayout offers me.

    Simple example

    import java.awt.BasicStroke;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import java.awt.Insets;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        public Test() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    new tttFrame();
                }
            });
        }
    
        public class tttFrame extends JFrame {
    
            public tttFrame() {
                this.getContentPane().add(new ttt());
                this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                this.setTitle("Tic-Tac-Toe");
    //            ImageIcon icon = new ImageIcon("ttt.png");
    //            this.setIconImage(icon.getImage());
                this.setBackground(new Color(0, 225, 237)); //Background color only works on JFrame
    //            this.setResizable(false);
                this.pack();
                this.setLocationRelativeTo(null);
                this.setVisible(true);
            }
        }
    
        public class ttt extends JPanel {
    
            private char playerChoice;
            private JPanel[][] tttGrid;
    
            public ttt() {
                setLayout(new GridBagLayout());
                GridBagConstraints gbc = new GridBagConstraints();
    
                tttGrid = new JPanel[3][3];
    
                gbc.gridy = 0;
                gbc.fill = gbc.BOTH;
                gbc.insets = new Insets(5, 5, 5, 5);
                for (int row = 0; row < 3; row++) {
                    gbc.gridx = 0;
                    for (int col = 0; col < 3; col++) {
                        CellPanel panel = new CellPanel();
                        add(panel, gbc);
                        tttGrid[row][col] = panel;
                        gbc.gridx += 1;
                    }
                    gbc.gridy += 1;
                }
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g;
                g2d.setColor(new Color(153, 250, 255));
                g2d.setStroke(new BasicStroke(5));
    
                int cellSize = 120 + 10;
                int boardSize = cellSize * 3;
    
                int x = (getWidth() / 2) - (cellSize / 2);
                int y = (getHeight() / 2) - (boardSize / 2);
    
                g2d.drawLine(x, y, x, y + boardSize);
                x += cellSize;
                g2d.drawLine(x, y, x, y + boardSize);
    
                x = (getWidth() - boardSize) / 2;
                y = (getHeight() / 2) - (cellSize / 2);
                g2d.drawLine(x, y, x + boardSize, y);
                y += cellSize;
                g2d.drawLine(x, y, x + boardSize, y);
            }
    
            public void gameStart() {
    
            }
    
            public void drawXO() {
    
            }
    
        }
    
        // You don't "need" to do this, but it ensures that each instance
        // is always the same
        public class CellPanel extends JPanel {
    
            public CellPanel() {
                setBackground(Color.GREEN);
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(120, 120);
            }
    
        }
    }
    

    One thing that this example does do, is demonstrates how flexible a layout manager can be, for example, the window is now re-sizable

    Resizable

    You could, with minimal effort, have the cells fill ALL of the available space as the window is resized, but, that's up to you