【问题标题】:how to add JPanels on JPanels while changing the background and making them visible如何在 JPanel 中添加 JPanel,同时更改背景并使它们可见
【发布时间】:2021-08-29 23:43:44
【问题描述】:

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

我的代码还在this.add(tttGrid[r][c]); 处引发了一个空指针异常。 tttGrid 基本上是 JPanel 的二维数组,旨在在用户/计算机单击时显示 X 或 O。我不确定我的代码如何导致 nullpointerexception。正如你在this.add(tttGrid[r][c]); 之前的行中所注意到的,我做了一些测试,我在控制台窗口中得到了这个输出:

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-loop重置r造成的
  • 你的下一个问题是,你正在与布局管理和绘画系统作斗争

标签: java swing nullpointerexception jpanel graphics2d


【解决方案1】:

所以,有几个复合问题。

首先,您的 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);
        }

    }
}

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

您可以通过最小的努力让单元格在调整窗口大小时填充所有可用空间,但这取决于您

【讨论】:

  • 那么setBounds() 是否可以与BorderLayout 等其他布局管理器一起使用?
  • @SpaceMonk 否 - 布局管理器接管了对子组件的定位和调整大小的控制,这是他们的工作 - 我强烈建议您学习如何使用它们,以及使用哪些取决于你想要达到的目标
猜你喜欢
  • 1970-01-01
  • 2016-02-15
  • 1970-01-01
  • 2016-07-26
  • 2014-05-28
  • 1970-01-01
  • 1970-01-01
  • 2015-12-10
  • 2010-10-07
相关资源
最近更新 更多