1

this may be a silly question but How do i call the paintComponent? Its not displaying the object at all. its within the, public class Ball extends JPanel implements Runnable.

public class Balls {

    public static void main(String[] args) {
        new Balls();
    }

    public Balls() {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                JFrame frame = new JFrame("Balls!");
                frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
                frame.add(new ballAdder());
                frame.setSize(1000, 1000);
                frame.setVisible(true);

            }
        });
    }

    public class ballAdder extends JPanel {

        public ballAdder() {
            add(new Ball(5, 5));

        }
    }

    public class Ball extends JPanel implements Runnable {

        public int x, y;
        public int speedx, speedy;
        public int width = 40, height = 40;

        public Ball(int x, int y) {
            this.x = x;
            this.y = y;
            new Thread(this).start();

        }

        public void move() {
            x += speedx;
            y += speedy;
            if (0 > x || x > 950) {
                speedx = -speedx;
            }
            if (0 > y || y > 950) {
                speedy = -speedy;
            }
            repaint();
        }

        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.BLACK);
            g.fillOval(x, y, width, height);
        }

        public void run() {
            while (true) {
                move();
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
Jole Mola
  • 25
  • 1
  • 8

1 Answers1

4

You should never call paintComponent (or paint) yourself. This is done by the RepaintManager.

The problem you're actually having is that fact speedx and speedy or 0, meaning you balls never move...

The other issue is that the ballAdder class is using a FlowLayout and you Ball class is not providing any details about it's preferred size, meaning that the Ball panel has a preferred size of 0x0.

Review

There is a significant issue with scalability with your design. Apart from the fact that you're going to find it difficult to add more then one ball to the UI because of layout issues...

Each Ball has it's own thread. This means, the more balls you add, the more threads that are going to be running. This is going to have a steady drain on resources and effect the performance of your application.

It would be better to provide a concept of a Drawable object which knew where it should be displayed within the concept it's container and could be painted from within the paintComponent. By utilising a single javax.swing.Timer it should be more capable of supporting a growing number of random balls.

First Fix

To fix you're first issue, you could do something like this...

public class ballAdder extends JPanel {
    public ballAdder() {
        setLayout(new BorderLayout());
        add(new Ball(5, 5));
    }
}

The problem with this fix is that you're only ever going to be able to have a single Ball on the container, as it will want to occupy the maximum available space.

You might want to have a read through Using Layout Managers for more details

A (possible) better solution

A (possible) better solution would be to use a single JPanel as a "ball pit" which maintained a reference to list of balls.

You would then use the BallPitPane's paintComponent method to draw all the balls (in the balls list).

Through the use of a single javax.swing.Timer, you could iterate through the balls list and update there positions (within the context of the BallPitPane

IMHO, this easier then trying to fight with the layout managers or writing your own...

enter image description here

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Bounce {

    public static void main(String[] args) {
        new Bounce();
    }

    public Bounce() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new BallPitPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class BallPitPane extends JPanel {

        private List<Ball> balls;
        private Random rand;

        public BallPitPane() {
            rand = new Random(System.currentTimeMillis());
            balls = new ArrayList<>(25);
            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (balls.isEmpty()) {
                        balls.add(new Ball(BallPitPane.this));
                    }

                    if (rand.nextBoolean()) {
                        balls.add(new Ball(BallPitPane.this));
                    }

                    for (Ball ball : balls) {
                        ball.move();
                    }
                    repaint();
                }
            });
            timer.start();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            for (Ball ball : balls) {
                ball.paint(g2d);
            }
            g2d.dispose();
        }
    }

    protected static int random(int min, int max) {

        return (int)Math.round(Math.random() * (max - min)) + min;

    }

    public static class Ball {

        public static final int WIDTH = 10;
        public static final int HEIGHT = 10;

        private int x;
        private int y;

        private int deltaX;
        private int deltaY;

        private Color color;
        private BallPitPane parent;

        public Ball(BallPitPane parent) {
            this.parent = parent;
            x = parent.getWidth() / 2;
            y = parent.getHeight() / 2;

            deltaX = random(-4, 4);
            deltaY = random(-4, 4);

            color = new Color(random(0, 255), random(0, 255), random(0, 255));
        }

        public void move() {
            x += deltaX;
            y += deltaY;

            if (x + WIDTH > parent.getWidth()) {
                x = parent.getWidth() - WIDTH;
                deltaX *= -1;
            } else if (x < 0) {
                x = 0;
                deltaX *= -1;
            }
            if (y + HEIGHT > parent.getHeight()) {
                y = parent.getHeight() - HEIGHT;
                deltaY *= -1;
            } else if (y < 0) {
                y = 0;
                deltaY *= -1;
            }
        }

        public Color getColor() {
            return color;
        }

        public void paint(Graphics2D g2d) {

            g2d.setColor(getColor());
            g2d.fillOval(x, y, WIDTH, HEIGHT);
            g2d.setColor(Color.BLACK);
            g2d.drawOval(x, y, WIDTH, HEIGHT);

        }        
    }    
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • its not being displayed in the first place tho – Jole Mola Jun 04 '13 at 01:33
  • It actually has a very small frame, fillRect(0, 0, 1000, 1000); System.out.println(getBounds()); returns java.awt.Rectangle[x=487,y=5,width=10,height=10] and a viewable rectangle but for some reason fillOval is not working. – arynaq Jun 04 '13 at 01:43
  • +1, The width and height exists because a FlowLayout provides a vertical/horizontal gap of 5 pixels around each component. The FlowLayout sets the size of the ball to its preferred size which is (0, 0), so there is nothing to paint. – camickr Jun 04 '13 at 01:45
  • it works when i place the paintComponet in the "ballAdder" class but not anywhere else, so confused – Jole Mola Jun 04 '13 at 01:48
  • @arynaq Try adding a border to the `Ball` panel, you'll see that it is never rendered... – MadProgrammer Jun 04 '13 at 01:48
  • @arynaq You've added `ballAdder` to the frame, which is using a `BorderLayout`, this automatically sizes the component to available space. You then add a `Ball` to the `ballAdder`, which is using a `FlowLayout`, which uses the `preferredSize` of the `Ball` to determine how big to make it. By default, all components have a preferred size if `0x0` – MadProgrammer Jun 04 '13 at 01:50
  • @MadProgrammer where do i set the perferredSize for the ball? in the JFrame? – Jole Mola Jun 04 '13 at 01:54
  • Using a `Timer` for the animation is a bad idea because it will not degrade gracefully. Here's something on this topic: http://gafferongames.com/game-physics/fix-your-timestep/ – Simon Jun 04 '13 at 02:06
  • @Simon That's a fair point, I had over 2000 balls bouncing without issues and it beats 2000 threads :P – MadProgrammer Jun 04 '13 at 02:07