java - 显示网格 Swing 定时器

标签 java performance swing user-interface timer

我感觉我不知道 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。我意识到它“运行”的部分实际上是每个单独的单元格更新其颜色所花费的时间,所以我的程序真的像现在这样慢得离谱且效率低下。

这是我提高速度和效率的想法。主要是,我会使用计时器延迟来处理下一次迭代的输出,然后下次计时器“触发”时,我会更改一个“滴答”变量,每个单元格将按照建议将其用作更改颜色的信号。为了实现这一点,我在每个单元格中添加了一个计时器(这个想法有多好/多坏?),它会稍微消磨时间,然后,在一个阻塞的 while 循环中,等着看内部的“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){}
        }
    }
}

在每个计时器“滴答”时,您首先重新绘制网格,然后处理下一次迭代以在下一个滴答上绘制。效率更高,并且即时更新。

我使用了一个修改过的 JPanel 作为主要的网格组件,它实现了一个 ActionListener 来处理用户在 gui 的其余部分以及每个计时器滴答上执行的每个 Action :

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
}

当然,您必须将板面板设置为计时器的 Action 监听器。

最佳答案

您在问题的第一部分中对 Timer 类的使用看起来确实是正确的。 java.swing.Timer 发生的事情是 ActionListener 以特定时间间隔在事件调度线程上触发,由 delay 参数指定。

这也意味着您放入 ActionListener 中的代码应该快速执行。当您的 ActionListener 代码正在执行时,UI 无法更新,因为 UI 线程(事件调度线程)正在执行 ActionListener 代码。 javadoc of that class 中清楚地记录了这一点.

Although all Timers perform their waiting using a single, shared thread (created by the first Timer object that executes), the action event handlers for Timers execute on another thread -- the event-dispatching thread. This means that the action handlers for Timers can safely perform operations on Swing components. However, it also means that the handlers must execute quickly to keep the GUI responsive.

这正是您在第一次更新时遇到的情况

new Timer(500, new ActionListener(){
  public void actionPerformed(ActionEvent e){
    //...
    while (c_tick != tick){}
    //...
  }
});

这里的 while 循环阻塞了事件调度线程。 c_tick != tick 检查永远不会改变,因为所涉及的变量仅在 EDT 上进行调整,并且您正在使用循环阻止它。

您的第二次更新似乎表明,通过从面板切换现在一切正常。然而,有两个看起来很奇怪的东西:

  1. catch ConcurrentModificationException cme 代码块。在您发布的代码中,我无法立即发现您会在哪里遇到 ConcurrentModificationException。请记住,Swing 是单线程的。所有可能与 Swing 组件交互的操作都应在 EDT 上执行,与多线程应用程序相比,遇到 ConcurrentModificationException 的机会要小得多。
  2. 你说

    Of course, you'd have to set the board panel as the action listener for the timer

    这似乎是不真实的。无论是 ActionListener 附加到 Timer 都需要交换当前格子和下一个格子,并计算下一个格子。一旦计算出下一个网格,就需要安排重新绘制网格面板。这个 ActionListener 是一个匿名/内部/单独的类还是网格面板本身是无关紧要的(至少在功能和设计方面,我永远不会选择让网格面板成为一个监听器)。

旁注:当您需要交换当前网格和新网格时,您可以使用以下代码

for (int y = 0; y < height; y++){
  for (int x = 0; x < width; x++){
    currentGrid[y][x] = newGrid[y][x];
  }
}

如果您仍然遇到性能问题,您可以尝试使用 System.arrayCopy,这可能比手动遍历数组快得多。

关于java - 显示网格 Swing 定时器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26067155/

相关文章:

optimization - 特定例程的性能指标 : any best practices?

java - 如何在maven应用程序中使用eclipse swing设计器?

c# - 用于 Java 的 OnPaint()?

java - 两个Java进程之间的快速通信

php - mt_rand() 和 rand() 的区别

python - python中的快速平方根反比来规范化向量

java - 即使绘制新文本,也设置一次 swing 应用程序的默认字体

java - Jsoup,确定超时和 404

java - 将另一个类的多个实例添加到 LinkedList 中

java - 如何删除IDEA中的所有注释行?