用于显示网格的摆动计时器

时间:2014-09-26 19:42:26

标签: java performance swing user-interface 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。我意识到它所在的部分&#34;运行&#34;实际上,每个单元格更新其颜色需要多长时间,所以我的程序实际上现在只是荒谬缓慢而且效率低下。

我的想法是提高速度和效率。主要是,我会使用定时器延迟来处理下一次迭代的输出,然后下一次定时器&#34;触发&#34;我会改变一个&#34; tick&#34;根据建议,每个单元格将用作改变颜色的信号的变量。为了做到这一点,我已经为每个单元添加了一个计时器(这个想法有多好/坏?),这会消耗一些时间,然后,在阻塞的while循环中,等待看到内部的&#34 ;蜱&#34;相当于全球&#34; tick&#34;并在发生这种情况时立即改变颜色。

最终结果是它一开始就会冻结。

这是我添加到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){}
        }
    }
}

在每个计时器上&#34;勾选&#34;首先重新绘制网格,然后处理下一个要在下一个刻度上绘制的迭代。更有效率,并立即更新。

我使用修改后的JPanel作为主网格组件,它实现了一个ActionListener来处理用户对其余gui以及每个计时器滴答的每个动作:

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
}

当然,您必须将电路板面板设置为计时器的动作监听器。

3 个答案:

答案 0 :(得分:2)

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

这也意味着您放在ActionListener中的代码应该快速执行。当您的ActionListener代码正在执行时,UI无法更新,因为UI线程(事件调度线程)被占用执行ActionListener代码。这在javadoc of that class中已明确记录。

  

虽然所有计时器都使用单个共享线程(由执行的第一个Timer对象创建)执行等待,但Timers的操作事件处理程序在另一个线程上执行 - 事件派发线程。这意味着Timers的动作处理程序可以安全地对Swing组件执行操作。但是,这也意味着处理程序必须快速执行以保持GUI响应。

这正是您在第一次更新中遇到的内容

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. 你说

      

    当然,您必须将电路板面板设置为定时器的动作监听器

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

  3. 附注:当您需要交换当前和新网格时,请使用以下代码

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

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

答案 1 :(得分:0)

这是一款生活游戏,以传统的Java Swing方式每半秒更新一次屏幕。

添加用于设置网格大小和更新速率的控件以及动画停止且可以使用鼠标设置单元格的编辑模式非常简单。要更改更新费率,请致电lifePane.run(newUpdateInterval)lifePane.run(0)暂停。致电lifePane.setGenSize(width, height)以更改网格。

使用单独的线程进行生成计算的主要价值(如我所建议的那样,但我还没有在这里完成)是在操作GUI时动画将继续。例如,如果使用滑块来控制速度,则动画将不会暂停,因为它将在UI线程中计算生成。

添加对于grins,我添加了控件并使用java.utils.timer而不是Swing计时器来获取this Gist中渲染的额外线程的效果。

但如果您在操作“鼠标按下”GUI项目时不介意暂停,单线程就可以了。我的旧笔记本电脑在Swing事件线程中以每秒20次更新的速度运行1000x1000的生成大小,GUI仍然表现得非常好。

方法update()从当前方法填充下一代,然后交换缓冲区。覆盖paintComponent只会绘制当前一代。使用此组合,所有计时器需要做的是updaterepaint

可能对您有用的其他约定是处理窗口大小调整和组织邻居计算的方法。了解好的习语有助于避免冗长的代码。

import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;

public class Life {

    protected LifePane lifePane;

    public static class LifePane extends JComponent {

        private int rows, cols;
        private byte[][] thisGen, nextGen;
        private Timer timer;

        public LifePane(int rows, int cols) {
            setGenSize(rows, cols);
        }

        public final void setGenSize(int rows, int cols) {
            this.rows = rows;
            this.cols = cols;
            thisGen = new byte[rows][cols];
            nextGen = new byte[rows][cols];
            Random gen = new Random();
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols; j++) {
                    thisGen[i][j] = toByte(gen.nextBoolean());
                }
            }
        }

        @Override
        protected void paintComponent(Graphics g) {
            int width = getWidth();
            int height = getHeight();
            // Clear the background.
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, width, height);
            // Set the 1-valued cells black.
            g.setColor(Color.BLACK);
            int y0 = 0;
            for (int i = 1; i < rows; i++) {
                int y1 = i * height / (rows - 1);
                int x0 = 0;
                for (int j = 1; j < cols; j++) {
                    int x1 = j * width / (cols - 1);
                    if (thisGen[i][j] != 0) {
                        g.fillRect(x0, y0, x1 - x0, y1 - y0);
                    }
                    x0 = x1;
                }
                y0 = y1;
            }
        }

        /**
         * Make the next generation current.
         */
        private void swapGens() {
            byte [][] tmp = thisGen;
            thisGen = nextGen;
            nextGen = tmp;
        }

        private static byte toByte(boolean booleanVal) {
            return booleanVal ? (byte) 1 : (byte) 0;
        }

        // Implementation of Conway's Game of Life rules.
        private void updateCell(int x0, int x, int x1, int y0, int y, int y1) {
            int n = thisGen[y0][x0] + thisGen[y0][x] + thisGen[y0][x1] +
                    thisGen[y] [x0] +                  thisGen[y] [x1] +
                    thisGen[y1][x0] + thisGen[y1][x] + thisGen[y1][x1];
            nextGen[y][x] = 
                (thisGen[y][x] == 0) ? toByte(n == 3) : toByte(n >> 1 == 1);
        }

        private void updateRow(int y0, int y, int y1) {
            updateCell(cols - 1, 0, 1, y0, y, y1);
            for (int j = 1; j < cols - 1; ++j) {
                updateCell(j - 1, j, j + 1, y0, y, y1);
            }
            updateCell(cols - 2, cols - 1, 0, y0, y, y1);
        }

        // Update the grid as a toroid and swap buffers.
        public void update() {
            updateRow(rows - 1, 0, 1);
            for (int i = 1; i < rows - 1; i++) {
                updateRow(i - 1, i, i + 1);
            }
            updateRow(rows - 2, rows - 1, 0);
            swapGens();
        }

        /**
         * Run the life instance with given update interval.
         * 
         * @param updateInterval interval in milliseconds, <= 0 to stop
         * @return this
         */
        public LifePane run(int updateInterval) {
            if (timer != null) {
                timer.stop();
                timer = null;
            }
            if (updateInterval > 0) {
                timer = new Timer(updateInterval, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        update();
                        repaint();
                    }
                });
                timer.start();
            }
            return this;
        }
    }

    public void run(int width, int height, int updateInterval) {
        JFrame frame = new JFrame("Life");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationByPlatform(true);
        lifePane = new LifePane(width, height).run(updateInterval);
        frame.setContentPane(lifePane);
        frame.setPreferredSize(new Dimension(1024, 800));
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Life().run(100, 100, 500);
            }
        });
    }
}

答案 2 :(得分:-1)

  

我计划稍后添加更多功能,以便让用户更好地控制各种变量,例如网格大小和迭代速度,但我想让显示器的核心功能正常工作。我很确定问题在于我如何使用Timer类,因为它的时间已经坏了。

这是一个很好的策略,该程序运行良好,但它可以更高效和可扩展。

例如,我建议使用自定义SwingWorker类来执行计算,然后将消息发送回UI。

以下是我如何在SwingWorker中创建此内容的示例。 以下是Oracle资源站点提供的附加信息:http://docs.oracle.com/javase/tutorial/uiswing/concurrency/simple.html

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;

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 stop;
    static int height = 20, width = 30, state;

    boolean run;

    private synchronized boolean getRun()
    {
        return run;
    }

    private synchronized void setRun(boolean run)
    {
        this.run = run;
    }

    /**
     * http://docs.oracle.com/javase/tutorial/uiswing/concurrency/simple.html
     *
     */
    SwingWorker worker = createNewWorker();

    private SwingWorker createNewWorker()
    {
        return
            new SwingWorker<Void, Void>()
            {
                protected Void doInBackground() throws Exception
                {
                    while(getRun())
                    {
                        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;
                                }
                            }
                        }

                        try
                        {
                            Thread.sleep(1000);
                        }
                        catch(InterruptedException e)
                        {
                            e.printStackTrace();
                        }
                    }

                    return null;
                }

                @Override
                protected void done()
                {
                    super.done();
                }
            };
    }

    public CA_Driver()
    {
        stop = false;
        setRun(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");

                    setRun(true);
                    worker = createNewWorker();
                    worker.execute();

//                  timer.start();

                    state += 1;
                }
                else
                {
                    start_pause.setText("Start");

                    setRun(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);

        worker.execute();

        /*
        //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(!getRun())
                    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);
        }
    }
}