Screen rotation timer

I get the feeling that I have no idea how the Swing timer works. I'm still new to the Java GUI API, and the program I am writing is just to test myself and help me get to know her internal developments better.

What he has to do is wait until the user presses the Start button, and then repeat the display (white or black JPanels grid), which displays a simple simulation of cellular machines with an interval of 1 second and pauses when there is a pause (similar to the button "Start", but changes the name). Each grid cell should start with a random color (white / black). Instead, it should stop for half a second or so, then “start” for another half second, then pause, then start, and so on and so forth.

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); } } } 

This is the timer section:

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

I plan to add additional features later to give the user more control over various variables, such as grid size and iteration speed, but I want the basic display functionality to work. I am sure the problem is how I use the Timer class, since this is the time that broke.

My first question is: am I using the Timer class correctly? If so, what is the problem? If not, how should I use it?

Update This is a good idea, MadProgrammer, and it's good to know that I am using Timer correctly. I realized that the part where it was “launched” was actually the time it took for each individual cell to refresh its color, so really my program is simply absurdly slow and inefficient, as it is now.

Here is my idea to improve speed and efficiency. Basically, I would use a timer delay to handle the output of the next iteration, and then when the timer “fires”, I would change the “tick” variable that each cell would use as its signal to change the color, as suggested. To do this, I added a timer to each cell (is it good or bad, is this an idea?), Which kills time a bit, and then in the lock cycle while expects the internal "tick" to be equivalent to the global "tick" and immediately change the color, when it will happen.

The end result is that it freezes as soon as it starts.

This is the timer that I added to the constructor of the Cell class:

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

And this is how I changed the global timer:

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

In addition to these changes, I removed the setColor() method in the Cell class. Can anyone point out the error I'm making?

UPDATE 2

I had to upgrade earlier, but just to put it, I found that this is not at all the way to do it. Instead of creating a panel with components and changing their backgrounds, you should simply draw the panel with a grid:

 @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){} } } } 

On each tick checkbox, you first redraw the grid, then process the next iteration, which will be drawn on the next tick. Much more efficient and instantly updated.

My I used a modified JPanel as the main component of the grid that implements an ActionListener to handle each action that the user performs on the rest of the gui, as well as for each timer:

 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 } 

Of course, you will need to set the panel pane as an action performer for the timer.

+5
source share
3 answers

Your use of the Timer class in the first part of the question really looks right. What happens to java.swing.Timer is that the ActionListener starts in the Thread Dispatch Thread at specific intervals specified by the delay parameter.

It also means that the code you put in the ActionListener should execute quickly. While your ActionListener code is ActionListener , the user interface cannot be updated as the user interface thread (Thread Dispatch Thread) is busy executing the ActionListener code. This is clearly described in the javadoc of this class .

Despite the fact that all timers fulfill their expectation using one common thread (created by the first executable Timer object), event handlers for timers are executed in another thread - the event dispatch thread. This means that timer action handlers can safely perform operations on Swing components. However, this also means that handlers must execute quickly in order to support the graphical interface.

This is exactly what you met in your first update

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

In a while loop here, you block Thread Dispatch Thread. Checking c_tick != tick will never change, since the variables involved will only be adjusted for EDT, and you will lock it with a loop.

Your second update seems to suggest that now everything works by switching from the panel. There are, however, two strange things:

  • Code block catch ConcurrentModificationException cme . In the published code, I cannot immediately determine where you encountered a ConcurrentModificationException . Remember that Swing is single threaded. All actions that can interact with Swing components must be performed on the EDT, which makes it possible to encounter a ConcurrentModificationException much less compared to a multi-threaded application.
  • You stated

    Of course you will need to set the panel bar as a timer action listener

    This seems wrong. Regardless of the ActionListener bound to the Timer , you need to change the current grid and the next grid and calculate the next grid. After calculating the next grid, you need to schedule a grid redraw. Regardless of whether this ActionListener an anonymous / inner / separate class or the grid panel itself, it does not matter (at least the functionality is a wise, reasonable design, I would never allow the grid panel to be a listener).

Side note: when you need to change the current and new grid, you use the following code

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

If you still have performance issues, you can try using System.arrayCopy , which is probably much faster than manually System.arrayCopy through an array.

+2
source

Here is a game of life that refreshes the screen every half second in the usual way of Java Swing.

It would be quite simple to add controls to set the grid size and refresh rate, as well as the editing mode when the animation stops and the cells can be set with the mouse. To change the refresh rate, call lifePane.run(newUpdateInterval) or lifePane.run(0) to pause. Call lifePane.setGenSize(width, height) to change the grid.

The main point when using a separate thread to calculate the generation (as suggested, but I did not) is that the animation will continue while you manipulate the graphical interface. For example, if you use a slider to control speed, the animation will not pause if it is expressed in the user interface stream.

Addition . For smiles, I added controls and used java.utils.timer instead of the Swing timer to get the effect of an extra stream for rendering to this Gist .

But if you don't mind pausing when manipulating the mouse down elements of the GUI, single-threaded processing is great. My old laptop has a size of 1000x1000 per second with 20 updates per second in the Swing event stream with a GUI that still behaves very well.

The update() method fills the next generation from the current one, and then swaps the buffers. Overriding paintComponent just draws the current generation. With this combination, all timers should do this update and repaint .

Other conventions that may be useful to you are a method for handling window resizing and organizing neighbor calculations. Knowing good idioms helps to avoid verbose code.

 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 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); } }); } } 
0
source

I plan to add additional features later to give the user more control over various variables, such as grid size and iteration speed, but I want the basic display functionality to work. I am sure the problem is how I use the Timer class, since this is the time that broke.

This is a good strategy, the program works well, but it can be more efficient and scalable.

For example, I recommend using the SwingWorker native class to do your calculations, and then send the message back to the user interface.

Here is an example of how I will create this in SwingWorker . Here is additional information available from the Oracle resource site: 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); } } } 
-1
source

Source: https://habr.com/ru/post/1203504/


All Articles