如何在更改背景并使它们可见的同时在 JPanels 上添加 JPanels

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

我正在尝试在 java 中使用 A.I 制作井字游戏,但我是 Java 的 Swing 和图形新手。首先,即使我设置了不透明 true(请参阅下面的 class ttt),我也无法更改 JPanel 的背景颜色。然而,我能够更改 JFrame 的背景(参见 class tttFrame)。其次,当我尝试将 JPanels(这些面板是 X 和 O 所在的位置)添加到我当前 JPanel 的顶部时 - 带有线条的那个(或者可能是,但 JPanels 的背景颜色不会改变) 他们不会显示。 testpaneltttGrid 中的面板都没有显示。我假设我对如何实现 JFrames 和 JPanels 有错误的想法。

我的代码还在 this.add(tttGrid[r][c]); 处抛出空指针异常。 tttGrid 基本上是 JPanel 的二维数组,当被 user/computer 单击时,这些 JPanel 旨在显示 X 或 O。我不确定我的代码如何导致空指针异常。正如您在 this.add(tttGrid[r][c]); 之前的行中注意到的那样,我做了一些测试,我在控制台上得到了这个输出 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();
  }
}

下图是我注释掉代码得到的this.add(tttGrid[r][c]);

所以,几个复杂的问题。

首先,您的 NPE 是由于您在复合 for-loops

的每次迭代中重置 r 值造成的
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++;
}

在这种情况下,r 总是 0

接下来,您将与面板的布局管理器发生冲突。 JPanel 默认使用 FlowLayout。在这种情况下设置组件的 bounds 将没有任何效果,并且 FlowLayout 管理器将尝试使用组件 preferredSize 来确定应该如何最佳布局。

这会给您带来不希望的结果,预计...

接下来,您将与绘画子系统作斗争。

通过覆盖 paint,您将完全控制组件及其子组件的绘制方式。如果你不想那么多控制,你应该先调用 super.paint 以确保油漆链得到维护。

但是,作为一般规则,您真的应该考虑覆盖 paintComponent(只是不要忘记先调用 super.paintComponent

看看:

了解更多详情。

可运行示例

以下示例使用了 GridBagLayout。这是一个更复杂的布局管理器,您可以使用 GridLayout 获得类似的结果,但我喜欢 GridBagLayout 为我提供的灵活性。

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

    }
}

这个例子做的一件事是演示布局管理器的灵活性,例如,window 现在可以重新调整大小

随着 window 调整大小,您可以轻松地让单元格填充所有可用的 space,但是,这取决于您