0

I'm trying to animate a rectangle based on a coordinate determined by for-loop, inside a button. Here is my JComponent Class:

public class Rect extends JComponent {
    public int x;
    public int y;
    public int w;
    public int h;
    
    public Rect (int x, int y, int w, int h) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        repaint();
    }

    @Override
    public void paintComponent(Graphics g)  {
        Graphics2D g2 = (Graphics2D) g;
        super.paintComponent(g);
        g2.setColor(Color.green);
        g2.drawRect(x+15, y+15, w, h);
    }
}

and here is my button and button inside JFrame class:

public class MainFrame extends JFrame {
    Rect R = new Rect(15, 15, 50, 50);
    JPanel lm = new JPanel();
    LayoutManager lay = new OverlayLayout(lm);
    JButton animate = new JButton("animate");

    public MainFrame () {
        setSize(1200, 700);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        lm.setLayout(lay);
        lm.add(R);
}
    animate.addActionListener(new ActionListener() {
           public void actionPerformed(ActionEvent e) { 
               for (int k = 0; k < 500; k+=50) {
                R = new Rect(k, k, 50, 50);
               validate();
               repaint();
               }
           }   
  });
}

But when I run the code and click the button, nothing happens. What's wrong?

EDIT: I run the frame inside my main class like this:

public class OrImage {
    public static void main(String[] args) throws Exception
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                MainFrame mf = new MainFrame();
                mf.setVisible(true);
            }
        });   
    }
}
Abra
  • 19,142
  • 7
  • 29
  • 41
  • 1
    Class `MainFrame` does not compile. What is `sg`? – Abra Nov 20 '20 at 07:27
  • @Abra I've updated the code. I run the `MainFrame` by using main method in other class. I've deleted the `sg` – Rainhard Calvin Nov 20 '20 at 07:36
  • I'm not sure but it looks like your `MainFrame` class still does not compile, even after your edit. – Abra Nov 20 '20 at 07:51
  • 1
    Animation in Swing is like manual animation. You create a series of still images that you change (repaint) quickly. Your drawing JPanel should draw an image based on the contents of a logical model. The values in the logical model are changed with a Swing Timer. – Gilbert Le Blanc Nov 20 '20 at 11:32

1 Answers1

1

I changed the code of class MainFrame such that when you press the animate button, something happens, but I don't know if that is what you want to happen.

I did not change class Rect and I added main() method to MainFrame just to keep everything in one class.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.LayoutManager;
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.OverlayLayout;

public class MainFrame extends JFrame {
    Rect R = new Rect(15, 15, 50, 50);
    JPanel lm = new JPanel();
    LayoutManager lay = new OverlayLayout(lm);
    JButton animate = new JButton("animate");

    public MainFrame () {
        setSize(1200, 700);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        lm.setLayout(lay);
        lm.add(R);
        animate.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) { 
                for (int k = 0; k < 500; k+=50) {
                    R = new Rect(k, k, 50, 50);
                    lm.add(R);
                }
                lm.revalidate();
                lm.repaint();
            }   
        });
        add(lm, BorderLayout.CENTER);
        add(animate, BorderLayout.PAGE_END);
        setLocationByPlatform(true);
        setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> new MainFrame());
    }
}

The main change is in method actionPerformed(). You need to add R to the JPanel. You need to call revalidate() on the JPanel because you have changed the number of components that it contains. And after calling revalidate() you should call repaint() (again, on the JPanel) to make it redraw itself.

This is how it looks before pressing animate.

before

And this is how it looks after pressing animate

after

EDIT

As requested – with animation.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.LayoutManager;
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.OverlayLayout;
import javax.swing.Timer;

public class MainFrame extends JFrame {
    Rect R = new Rect(15, 15, 50, 50);
    JPanel lm = new JPanel();
    LayoutManager lay = new OverlayLayout(lm);
    JButton animate = new JButton("animate");
    private int  x;
    private int  y;
    private Timer  timer;

    public MainFrame () {
        setSize(1200, 700);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        lm.setLayout(lay);
        lm.add(R);
        timer = new Timer(500, event -> {
            if (x < 500) {
                lm.remove(R);
                x += 50;
                y += 50;
                R = new Rect(x, y, 50, 50);
                lm.add(R);
                lm.revalidate();
                lm.repaint();
            }
            else {
                timer.stop();
            }
        });
        timer.setInitialDelay(0);
        animate.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                timer.start();
            }   
        });
        add(lm, BorderLayout.CENTER);
        add(animate, BorderLayout.PAGE_END);
        setLocationByPlatform(true);
        setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> new MainFrame());
    }
}
Abra
  • 19,142
  • 7
  • 29
  • 41
  • Well, thanks for the correction. Actually, my codes are quite long so I needed to declutter a lot before I post here. Your demonstration is just exactly what I have in mind, but I wonder is there any way so that I don't have to add `R` to the `panel` inside every loop? Because I just want to make the rectangle move, not multiplying. – Rainhard Calvin Nov 20 '20 at 08:52
  • @RainhardCalvin For animation in _Swing_ you should use a [Swing timer](https://docs.oracle.com/javase/tutorial/uiswing/misc/timer.html) – Abra Nov 20 '20 at 08:55
  • I thought Swing Timer is just for add the delay before a task and after another. Perhaps I can delete or dispose the rectangle after every loop to make it looks animated? – Rainhard Calvin Nov 20 '20 at 08:56
  • If I may, there's one thing still boggling me; in my full code the for loop is in another `class`. So I wanted to just call that `class`'s method inside the button and I put the `add`, `revalidate`, and `repaint` inside that loop by `MainFrame mf = new Mainframe();` then `mf.lm.add(R)`, `mf.lm.revalidate();`, `mf.lm.repaint();`, but nothing happened. Is my code wrong? – Rainhard Calvin Nov 20 '20 at 09:34
  • @RainhardCalvin if you want help debugging your code, then either post a new question or [edit] the code in your question, above. In either case, make sure you post a [mcve]. – Abra Nov 20 '20 at 09:36
  • I just post a New Question. Thank you – Rainhard Calvin Nov 20 '20 at 09:57