我有一种感觉,我不知道swing Timer如何工作。 我还是Java GUI API的新手,我正在编写的程序仅用于测试自己并帮助我更熟悉其内部工作。
它的作用是等待用户按下“开始”按钮,然后迭代显示(一个白色或黑色JPanel的网格),在1秒间隔内显示简单的元胞自动机模拟,并在按下暂停按钮时暂停(与开始按钮相同,但更改名称)。 网格中的每个单元格都应以随机颜色(白色/黑色)开始。 它实际上是暂停了大约半秒钟,然后“运行”另外半秒钟,然后暂停,然后再运行,如此往复。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CA_Driver extends JFrame{
private JPanel gridPanel, buttonPanel;
private JButton start_pause, pause;
private static Timer timer;
private Color black = Color.black;
private Color white = Color.white;
static Color[][] currentGrid, newGrid;
static Cell[][] cellGrid;
static boolean run, stop;
static int height = 20, width = 30, state;
public CA_Driver(){
stop = false;
run = false;
currentGrid = new Color[height][width];
newGrid = new Color[height][width];
cellGrid = new Cell[height][width];
//Initialize grid values
for (int x = 0; x < currentGrid.length; x++)
for (int y = 0; y < currentGrid[x].length; y++){
int z = (int) (Math.random() * 2);
if (z == 0)
currentGrid[x][y] = newGrid[x][y] = white;
else currentGrid[x][y] = newGrid[x][y] = black;
}
//Create grid panel
gridPanel = new JPanel();
gridPanel.setLayout(new GridLayout(height,width));
//Populate grid
for (int x = 0; x < newGrid.length; x++)
for (int y = 0; y < newGrid[x].length; y++){
cellGrid[x][y] = new Cell(x,y);
cellGrid[x][y].setBackground(newGrid[x][y]);
int z = (int) Math.random();
if (z == 0) cellGrid[x][y].setBackground(black);
else cellGrid[x][y].setBackground(currentGrid[x][y]);
gridPanel.add(cellGrid[x][y]);
}
//Create buttons
state = 0;
start_pause = new JButton();
start_pause.setText("Start");
start_pause.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
if (state == 0) {
start_pause.setText("Pause");
run = true;
timer.start();
state += 1;
}
else {
start_pause.setText("Start");
run = false;
timer.stop();
state -= 1;
}
}
});
buttonPanel = new JPanel(new BorderLayout());
buttonPanel.add(start_pause, BorderLayout.NORTH);
// buttonPanel.add(pause, BorderLayout.EAST);
//Initialize and display frame
this.add(gridPanel, BorderLayout.NORTH);
this.add(buttonPanel, BorderLayout.SOUTH);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
//this.setSize(500, 500);
pack();
this.setVisible(true);
//Initialize timer
timer = new Timer(1000, new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
for (int x = 0; x < cellGrid.length; x++)
for (int y = 0; y < cellGrid[x].length; y++){
cellGrid[x][y].setColor();
currentGrid[x][y] = newGrid[x][y];
}
//Display processing for next frame
for (int x = 0; x < currentGrid.length; x++)
for (int y = 0; y < currentGrid[x].length; y++){
int b = checkNeighbors(y,x);
if (b > 4 || b < 2)
newGrid[x][y] = black;
else newGrid[x][y] = white;
}
if(!run) timer.stop();
}
});
}
public static void main(String[] args) {
new CA_Driver();
}
private int checkNeighbors(int w, int h){
int b = 0;
//Top Left
if((w != 0) && (h != 0) && (currentGrid[h - 1][w - 1] == black))
b++;
//Top Middle
if((h != 0) && (currentGrid[h - 1][w] == black))
b++;
//Top Right
if((w != width - 1) && (h != 0) && (currentGrid[h - 1][w + 1] == black))
b++;
//Middle Left
if((w != 0) && (currentGrid[h][w - 1] == black))
b++;
//Middle Right
if((w != width - 1) && (currentGrid[h][w + 1] == black))
b++;
//Bottom left
if((w != 0) && (h != height - 1) && (currentGrid[h + 1][w - 1] == black))
b++;
//Bottom Middle
if((h != height - 1) && (currentGrid[h + 1][w] == black))
b++;
//Bottom Right
if((w != width - 1) && (h != height - 1) && (currentGrid[h + 1][w + 1] == black))
b++;
return b;
}
private class Cell extends JPanel{
private Color c;
private int posx, posy;
public Cell(int x, int y){
posx = x;
posy = y;
}
public Point getLocation(){
return new Point(posx, posy);
}
public void setColor(){
c = newGrid[posx][posy];
setBackground(c);
}
public Dimension getPreferredSize(){
return new Dimension(10,10);
}
}
}
这是计时器部分:
timer = new Timer(1000, new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
for (int x = 0; x < cellGrid.length; x++)
for (int y = 0; y < cellGrid[x].length; y++){
cellGrid[x][y].setColor();
currentGrid[x][y] = newGrid[x][y];
}
//Display processing for next frame
for (int x = 0; x < currentGrid.length; x++)
for (int y = 0; y < currentGrid[x].length; y++){
int b = checkNeighbors(y,x);
if (b > 4 || b < 2)
newGrid[x][y] = black;
else newGrid[x][y] = white;
}
if(!run) timer.stop();
}
});
我计划稍后添加更多功能,以便用户可以对各种变量(如网格大小和迭代速度)进行更多控制,但我想先让显示的核心功能正常工作。我相当确定问题出在我如何使用Timer类上,因为时间不准确。
我的第一个问题是:我是否正确使用了Timer类?如果是这样,那么问题出在哪里?如果不是,我应该如何使用它?
更新 这是个好主意,MadProgrammer,并且很高兴知道我正在正确使用Timer。我意识到它“运行”的部分实际上是每个单元格更新其颜色所需的时间,因此现在我的程序非常缓慢和低效。
这是我提高速度和效率的想法。主要是,我将使用计时器延迟来处理下一次迭代的输出,然后下一次计时器“触发”时,我会更改一个“tick”变量,每个单元格都会将其用作更改颜色的信号,如建议所述。为了实现这一点,我已经向每个单元格添加了一个计时器(这是一个好/坏的主意吗?),它会等待一段时间,然后在阻塞的while循环中等待看到内部的“tick”等于全局“tick”,并在发生这种情况时立即更改颜色。
最终结果是它一启动就会冻结。
这是我添加到Cell类构造函数的计时器:
c_timer = new Timer(500, new ActionListener(){
public void actionPerformed(ActionEvent e){
c_timer.stop();
while (c_tick != tick);
setBackground(currentGrid[posx][posy]);
c_tick = 1 - c_tick;
if(run) timer.restart();
}
});
c_timer.start();
我修改了全局计时器的方法如下:
timer = new Timer(1000, new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
currentGrid[y][x] = newGrid[y][x];
tick = 1 - tick;
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++){
if (b[y][x] > 6 || b[y][x] < 1) newGrid[y][x] = white;
else newGrid[y][x] = black;
}
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
b[y][x] = checkNeighbors(x,y);
if(!run) timer.stop();
}
});
除了这些更改,我还删除了Cell类中的setColor()方法。有人能指出我的错误吗?
更新2: 我应该早点更新,但简单来说,我发现这完全是错误的做法。而不是制作一个充满组件并更改它们背景的面板,你应该使用网格来绘制面板。
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
for (int h = 0; h < board_size.height; h++){
for (int w = 0; w < board_size.width; w++){
try{
if (grid[h][w] == BLACK)
g.setColor(BLACK);
else g.setColor(WHITE);
g.fillRect(h * cell_size, w * cell_size, cell_size, cell_size);
} catch (ConcurrentModificationException cme){}
}
}
}
在每个计时器“tick”上,您首先重新绘制网格,然后处理下一个要在下一个tick上绘制的迭代。这样更有效率,并且可以立即更新。
我使用了一个修改过的JPanel作为主网格组件,它实现了一个ActionListener来处理用户在GUI的其余部分执行的每个操作以及每个计时器tick。
public void actionPerformed(ActionEvent e) {
//Timer tick processing: count surrounding black cells, define next iteration
//using current rule set, update master grid
if (e.getSource().equals(timer)){
//Processing for each tick
}
else if(e.getSource()...
//Process events dispached by other components in gui
}
当然,你需要将面板设置为计时器的操作监听器。
Timer
实例都是一个新的Thread
。而且我无法理解你的问题是什么? - SeniorJDupdate()
函数,使“世界数据结构”变为“当前”。在你的情况下,数据结构是CA,“current”是细胞的下一个配置。像你展示的那样,paint函数只是绘制数据结构,即网格。现在,计时器函数只需调用update()
,然后调用repaint()
。这是一种非常简单和模块化的动画思考方式。 - Gene