0

I'm trying to make a GUI where it has essentially has multiple bouncing balls. The balls are added via a JButton. I was able to make the Ball class successfully and have it animate on the screen with only one ball, however I was not able to get multiple balls added via the use of the button. I have tried making an ActionListener which creates new threads and calls on SwingUtilities.InvokeLater, but it only made the GUI freeze. I have tried following this guide on how to use InvokeLater: http://www.javamex.com/tutorials/threads/invokelater.shtml

Here is my code so far. Any help is appreciated. (I realize that the bouncing ball question has been asked here before, but I was not able to understand the methods explained in the replies and thought I would ask here rather than comment on a 2 year old question)

Ball Class

package BouncingBall;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.Random;

public class Ball extends JComponent
{
    private double dx;
    private double dy;
    private double x;
    private double y;
    private Color color;
    private Random rand = new Random();
    private Ellipse2D.Double ball;
    private final static int diam = 10;
    private final static int xLim = BallFrame.FRAME_WIDTH-diam;
    private final static int yLim = BallFrame.FRAME_HEIGHT-diam*7;
    boolean xUpperBound = true;
    boolean yUpperBound = true;

    public Ball()
    {
        color = new Color(rand.nextFloat(),rand.nextFloat(),rand.nextFloat());
        x = rand.nextInt(xLim);
        y = rand.nextInt(yLim);
        dx = rand.nextInt(9)+1;
        dy = rand.nextInt(9)+1;
    }

    public void paintComponent(Graphics g)
    {
        Graphics2D g2 = (Graphics2D) g;
        draw(g2);
    }

    public void draw(Graphics2D g2)
    {
        ball = new Ellipse2D.Double(x,y,diam,diam);
        g2.setColor(color);
        g2.fill(ball);
    }

    public void move()
    {
        if (((x+dx) < xLim) && xUpperBound)
            x+=dx;
        else if (x > 0)
        {
            xUpperBound = false;
            x-=dx;
        }
        else
            xUpperBound = true;

        if (((y+dy) < yLim) && yUpperBound)
            y+=dy;
        else if (y > 0)
        {
            yUpperBound = false;
            y-=dy;
        }
        else
            yUpperBound = true;
    }

    public void animate()
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    while (true)
                    {
                        repaint();
                        move();
                        Thread.sleep(40);
                    }
                }
                catch (InterruptedException e)
                {
                    System.out.println("Thread was interrupted!");
                }
            }
        });
    }
}

BallFrame Class

package BouncingBall;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

public class BallFrame extends JFrame
{
    public final static int FRAME_WIDTH = 800;
    public final static int FRAME_HEIGHT = 600;

    public BallFrame()
    {
        class ballListener implements ActionListener
        {
            @Override
            public void actionPerformed(ActionEvent actionEvent)
            {
                Thread thread = new Thread()
                {
                    public void run()
                    {
                        Ball temp = new Ball();
                        temp.animate();
                        add(temp);
                    }
                };
                thread.start();
            }
        }

        setLayout(new BorderLayout());
        JButton addBall = new JButton("Add Ball");
        ActionListener listener = new ballListener();
        addBall.addActionListener(listener);
        add(addBall, BorderLayout.SOUTH);
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                BallFrame frame = new BallFrame();
                frame.setSize(FRAME_WIDTH, FRAME_HEIGHT );
                frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
                frame.setVisible( true );
            }
        });
    }
}
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
Mo2
  • 1,090
  • 4
  • 13
  • 26
  • `Thread.sleep(40);` This is the wrong way to go about animation. I would also not have the `Ball` class extend `JComponent` and not try to (itself) perform a drawing loop. Instead, use a Swing `Timer` in the `BallPanel` (new) class, and have it call the `Ball.move()` method before `Ball.draw(g)`.. The `BallPanel` should also return a sensible preferred size, and after being added to the frame, the frame can be adjusted to size using `pack()`. – Andrew Thompson May 22 '14 at 05:36
  • I thought about using the timer as well, but it was suggested to use the Thread.sleep in the assignment's PDF. The point is to use multithreading here though, so either way works. As for not extending JComponent, can you explain why? I've never done any advanced operations with swing and so almost everything I've done was with the use of extending JComponent. – Mo2 May 22 '14 at 05:41

1 Answers1

2

A problem I already in design, is the fact that you are making Ball extends JComponent which already limits you in many ways. You should instead just make Ball a simple state-holder/state-manipulation class, with a method to animate and draw the ball state.

Then just have one JComponent/JPanel class for the painting of the balls. In that class you can have a List<Ball> that you will draw in the paintComponent method. Whenever you want to add a ball, just add a Ball to the list.

Also, instead of using separate threads, which shouldn't be done with painting (as all painting should be done on the EDT), you should instead use a javax.swing.Timer for the timing animation. You can see more at How to Use Swing Timers

You can also see a bunch of examples of the above methods here and here and here and here and here and here.


UPDATE

"Can you explain extending JComponent limits it? As for not using threads. How would I have multiple Ball objects animate independently? Doesn't that require multiple threads?"

  1. You have to add multiple components to one visible "painting surface". So basically you will have to layer these component to the surface, having to deal with component opacity, among other things.
  2. You use the javax.swing.Timer, in which you will loop through the List and call each ball's animate() method.
  3. NO.

See the examples from the links I posts. They are have examples of using the timer and a list of objects

Here's the basic idea in some snippets of code

Ball class

public class Ball {
    public void draw(Graphics g) {}
    public void animate() {}
}

BallPanel class

public class BallPanel extends JPanel {
    List<Ball> balls;

    public BallPanel() {
        Timer timer = new Timer(40, new ActionListener(){
            public void actionPerformed(ActionEvent e) {
                for (Ball ball : balls) {
                    ball.animate();
                }
                repaint();
            }
        });
        timer.start();
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        for (Ball ball : ball ) {
            ball.draw(g);
        }
    }

    public void addBall(...) {
        balls.add(new Ball(..));
    }
}
Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Can you explain extending JComponent limits it? As for not using threads. How would I have multiple Ball objects animate independently? Doesn't that require multiple threads? – Mo2 May 22 '14 at 05:45
  • Thank you for that. After looking at your examples the concept has started to become clearer to me. The only limitation is that this was for an assignment which specifically required the use of Threads since the topic was about multithreading. Any ideas? – Mo2 May 22 '14 at 05:54
  • Does the requirement state to use a different thread for each ball? – Paul Samsotha May 22 '14 at 05:59