2

How can I use repaint here, that it changes the display after some interval, like an animation?

Ouput:

(Like initially and randomly it is) this-1 (after some short interval and randomly it changes to) this-2 and continued...

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

class MatrixPanel extends JPanel {

    private final int sqW = 15;
    private final int sqH = 15; 

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(300, 300);
    }

    @Override
    public void paintComponent(Graphics g) {
        Random rand = new Random();
        for (int i = 0; i < 300; i += this.sqW) {
            for (int j = 0; j < 300; j += this.sqH) {
                if (rand.nextInt(2) == 1) {
                    g.setColor(Color.BLACK);
                } else {
                    g.setColor(Color.GRAY);
                }
                g.fillRect(i, j, this.sqW, this.sqH);
                g.setColor(Color.BLACK);
                g.drawRect(i, j, this.sqW, this.sqH);
            }
        }
    }
}

class GameOfLifeGUI extends JFrame {

    public GameOfLifeGUI() {

        JSplitPane splitPane = new JSplitPane();

        /*Panels*/
        JPanel topPanel = new JPanel();
        JPanel bottomPanel = new JPanel();
        
        /*Label - 1*/
        JLabel generationLabel = new JLabel("GenerationLabel");
        generationLabel.setText("Generation #");
        generationLabel.setFont(new Font("Comic Sans MS", Font.PLAIN, 14));
        generationLabel.setBounds(10, 5, 300, 20);

        /*Label - 2*/
        JLabel aliveLabel = new JLabel("AliveLabel");
        aliveLabel.setText("Alive: ");
        aliveLabel.setFont(new Font("Comic Sans MS", Font.PLAIN, 14));
        aliveLabel.setBounds(10, 25, 300, 20);

        topPanel.setLayout(null);
        topPanel.add(generationLabel);
        topPanel.add(aliveLabel);

        bottomPanel.add(new MatrixPanel());

        setPreferredSize(new Dimension(315, 405));

        getContentPane().setLayout(new GridLayout());

        getContentPane().add(splitPane);

        splitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
        splitPane.setDividerLocation(50);
        splitPane.setTopComponent(topPanel);
        splitPane.setBottomComponent(bottomPanel);

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        pack();
    }

    public static void main(String[] args) {
        new GameOfLifeGUI().setVisible(true);
    }
}

Specifically not repaint, if there are other components that I can use and meet my requirements will be most welcome.

Thank you.

Leprachon
  • 117
  • 1
  • 8
  • (1+) Good, you updated the code to override the `getPreferredSize()` method. That is the better approach for using Swing correctly. *Do I have to use layout here?* - the general answer is yes, always use layout managers.. In this case most of your code is using a layout manager. By default a `JPanel` uses a `FlowLayout`, which respects the size of the MatrixPanel, now that you implemented the `getPreferredSize()` method. That is why your code works as expected. – camickr Aug 25 '20 at 18:17
  • However, your "topPanel" is using a null layout and setBounds(). That is not a good practice and should be avoided. So yes, your topPanel should use a layout manager. Based on your current code you could easily use a `BoxLayout`. Read the section from the Swing tutorial on [Layout Managers](https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html) for more information and working example. If you have problems then post a new question with an example that just uses your *top panel*, since you will only use the layout manager to display the two labels, – camickr Aug 25 '20 at 18:17
  • Also most people wound probably not use a JSplitPane for this. Instead you keep the default BorderLayout of the JFrame. Then you add the "topPanel" to the `BorderLayout.PAGE_START` and the MatrixPanel to the `BorderLayout.CENTER`. – camickr Aug 25 '20 at 18:23
  • Thank you so much @camickr , for all the points. I will really try to rectify all the mistakes. – Leprachon Aug 25 '20 at 18:32

1 Answers1

0

You don't want to do the following:

    public void paintComponent(Graphics g) {
        Random rand = new Random();
        for (int i = 0; i < 300; i += this.sqW) {
            for (int j = 0; j < 300; j += this.sqH) {
                if (rand.nextInt(2) == 1) {
                    g.setColor(Color.BLACK);
                } else {
                    g.setColor(Color.GRAY);
                }
                g.fillRect(i, j, this.sqW, this.sqH);
                g.setColor(Color.BLACK);
                g.drawRect(i, j, this.sqW, this.sqH);
            }
        }
    }

Since painting is done on the EDT, you need to keep your processing to a minimum, otherwise performance will suffer and you may even lock up your application.

Use a Swing timer and create the image in a BufferedImage object. BufferedImages provide a graphics context so you can use the same methods you are using in paintComponent.

You can set the timer interval to whatever works for you and then issue a repaint when the image is created. So it could look similar to the following:

final BufferedImage image = new BufferedImage(300,300,BufferedImage.TYPE_INT_RGB);
...     
Timer timer = new Timer(0, ae->createImage());
timer.setDelay(300);
timer.start();
...  
    
public void createImage(BufferedImage image) {
    Random rand = new Random();
    Graphics g = image.getGraphics();
    for (int i = 0; i < 300; i += this.sqW) {
        for (int j = 0; j < 300; j += this.sqH) {
            if (rand.nextInt(2) == 1) {
                g.setColor(Color.BLACK);
            } else {
                g.setColor(Color.GRAY);
            }
            g.fillRect(i, j, this.sqW, this.sqH);
            g.setColor(Color.BLACK);
            g.drawRect(i, j, this.sqW, this.sqH);
        }
    }
    repaint();
}
    
    
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    // Image observer not required so it can be set to null.
    g.drawImage(image, 0,0, null);
}

Finally, you need to set your MatrixPanel size large enough to hold the image. Probably 300 x 300.

WJS
  • 36,363
  • 4
  • 24
  • 39
  • I tried implementing as you specified but the frame just got one box in the middle and blinks randomly which I think is because of timer but the frame doesn't paint all the boxes. – Leprachon Aug 25 '20 at 15:10
  • Did you do something like this. `matrixPanel = new MatrixPanel(); matrixPanel.setPreferredSize(new Dimension(300,300)); bottomPanel.add(matrixPanel);` – WJS Aug 25 '20 at 15:16
  • @Leprachon don't use the above suggestion to set the preferred size of the matrix panel. Your current code which overrides the `getPreferredSize()` method is the better way to do this. Each component should determine its own preferred size. Update your question with the updated code. – camickr Aug 25 '20 at 15:35
  • Yeah. Exactly like that. But instead of `setPreferredSize()`, I used `setSize()`. Does it has to be done like that? – Leprachon Aug 25 '20 at 15:37
  • @Leprachon, No. Don't use setSize() or don't use setPreferredSize(). It is the job of the layout manager to set the size/location of components based on the rules of the layout manager. As I stated in my above comment. Overriding the `getPreferredSize()` method is the proper design. – camickr Aug 25 '20 at 15:40
  • According to [this](https://stackoverflow.com/a/1783880/1552534), use `setSize()` with no layout manager and use `setPreferredSize()` with a layout manager. – WJS Aug 25 '20 at 15:45
  • @WJS, yes it explains the difference between the 2, but does not get into best practices. That thread is over a decade old and we have been trying to promote the proper practice in this forum for longer than that. For example, you don't set the preferred size of a JTextArea. You set the rows/column. Then the text area will determine the "preferred" size based on the rows/column and Font being used. So the preferred size can change dynamically and your application code should not be be randomly setting the preferred size. – camickr Aug 25 '20 at 16:13
  • @Leprachon, yes, it works, but it will also work when you implement `getPreferredSize()`!!! Again, what happens if you decide you want a grid of 30 x 30. With your current implementation you need to change the MatrixPanel class and the GameOfLifeGUI class. You should NOT need to make changes in two places. That is not how components are designed to work. Also, what if you want to change the square size to 10 from 15. Again same concern. Think of this as a Font size change. You don't keep setting the preferred size of a JLabel if you change the Font. – camickr Aug 25 '20 at 16:16
  • @camickr Do I have to use layout here? I get the disadvantage of using `setPreferredSize()`. Can you guide me a bit here please? How can I achieve the requirements? – Leprachon Aug 25 '20 at 17:29
  • @leprachon I have provided you an answer on the animation aspect of your class and you said you had it working. There are many aspects of painting that you need to learn. If you have specific question on other aspects it would be best to ask a new question. – WJS Aug 25 '20 at 17:45
  • @Leprachon I appreciate the `accept` but that is not what I was getting at. You may want to research and then ask questions regarding when and when not to use layout managers. You can use both too. It depends on the requirements. – WJS Aug 25 '20 at 17:55