我正在制作俄罗斯方块游戏的副本并创建了一个游戏面板。
我试图交换
tetris.initGamePanel();
和 tetris.initWindow();
的顺序
public static void main(String[] args){
Tetris tetris = new Tetris();
tetris.initGamePanel();
tetris.initWindow();//Why the order matters???
//tetris.initGamePanel();
}
发现当先执行tetris.initGamePanel();
时,程序运行得很好,但是当顺序改为相反时,出现了一个空白窗口。于是我尝试调试代码,发现剩下的代码在
public void initGamePanel() {
JPanel game_main = new JPanel();
game_main.setLayout(new GridLayout(game_x,game_y,1,1));
//initialize game panel
for(int i = 0; i < text.length; i++) {
for (int j = 0; j < text[i].length; j++) {
//create a new JTextArea Object that contains parameter i and j
text [i][j] = new JTextArea();
text [i][j].setBackground(Color.white);
text [i][j].addKeyListener(this);
text [i][j].setEditable(false);
if(j == 0 || j == text[i].length-1 || i == text.length-1) {
text [i][j].setBackground(Color.MAGENTA);
data [i][j] = 1;
}
game_main.add(text[i][j]);
}
}
this.setLayout(new BorderLayout());
this.add(game_main,BorderLayout.CENTER);
}
在创建 JPanel 实例后被跳过
public void initGamePanel(){
JPanel game_main = new JPanel();
但奇怪的是,尽管其余的代码甚至都没有完成,但一切似乎都运行良好(按正确的顺序)。这是完整的代码:
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Tetris extends JFrame implements KeyListener {
private static final int game_x = 24;
private static final int game_y = 12;
public Tetris(){
text = new JTextArea[game_x][game_y];
data = new int[game_x][game_y];
//initGamePanel();
}
JTextArea[][] text;
int [][] data;
public void initWindow(){
this.setSize(400,800);
this.setLocationRelativeTo(null);//align to the center if the component is set to null
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.setTitle("Tetris");
}
public void initGamePanel(){
JPanel game_main = new JPanel();
game_main.setLayout(new GridLayout(game_x,game_y,1,1));
//initialize game panel
for(int i = 0; i < text.length; i++){
for (int j = 0; j < text[i].length; j++){
//create a new JTextArea Object that contains parameter i and j
text [i][j] = new JTextArea();
text [i][j].setBackground(Color.white);
text [i][j].addKeyListener(this);
text [i][j].setEditable(false);
if(j == 0 || j == text[i].length-1 || i == text.length-1 ){
text [i][j].setBackground(Color.MAGENTA);
data [i][j] = 1;
}
game_main.add(text[i][j]);
}
}
this.setLayout(new BorderLayout());
this.add(game_main,BorderLayout.CENTER);
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
public static void main(String[] args){
Tetris tetris = new Tetris();
tetris.initGamePanel();
tetris.initWindow();//Why the order matters???
//tetris.initGamePanel();
}
}
我认为这是因为 JPanel 的构造函数以某种方式执行了其余的代码,但是如何执行?
initGamePanel
正在更新 UI。 如果在
initWindow
之前调用,则会计算 UI 的布局,并将在窗口变得可见的过程中进行绘制。如果您反转调用,则不会告诉系统 UI 的状态已更改,因此它不会执行这些操作。
Swing 很懒。 当您更改 UI 的状态(尤其是布局(即添加/删除组件))时,您必须通知系统应执行新的布局和绘制通道。
您应该在
revalidate
方法的末尾添加对
repaint
和
initGamePanel
的调用,例如...
public void initGamePanel(){
// ...
revalidate();
repaint();
}
this.setSize(400,800);
是个坏主意。 窗口可查看的内容将是窗口的大小减去窗口装饰插图。相反,您应该使用布局管理系统,并在窗口上调用
pack
(但您实际上应该在建立基本布局后才执行此操作)。我还会将
text [i][j] = new JTextArea();
更改为
text [i][j] = new JTextArea(1, 1);
之类的内容,以便让
JTextArea
报告合适的首选尺寸。 不知道为什么你要使用这个,似乎对于任务来说过于复杂,但这就是我。
JTextArea
对于监控键盘输入来说也是一个糟糕的选择。 我可能会考虑使用 text [i][j].addKeyListener(this);
,或者,如果您不使用文本输入组件,请使用键绑定 API
,这将使您避免许多看似随机的问题。在窗口可见后调用
DocumentListener
时出现问题,这改变了窗口装饰的大小并做了各种奇怪的事情。 作为用户,如果您不这样做,而是广泛使用布局管理 API,以便在调整窗口大小时更好地管理组件,我将不胜感激。
此外,
this.setResizable(false);
所做的工作确实应该在构造函数中完成,这样在构造函数返回时 UI 就已经准备好了。
您还应该避免从顶级容器(例如
initGamePanel
)进行扩展。您没有向类添加新功能,而是将自己锁定在单一使用上下文中。 JFrame
也是一个复杂的复合组件,通常最好尽可能避免这种混乱。