Gil Peretz
Gil Peretz

Reputation: 2429

add object to JPanel

I don't usually do this, and I hate when other does... But I found my self spending the last 6 hours to figure out where I wrong. I rewrote those lines dozen of times, change several approaches, and still couldn't figure out what the hake is wrong!

The issue is as followed: Clicking on the [Add] button should add additional circle to the panel (starting from x=0,y=0 - top left corner).

currently only the first circle is loaded, the rest is loaded to the arrayList but not to the panel.

Any thoughts?

BallControl:

    import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.event.*;
import java.awt.*;
import java.util.ArrayList;

public class BallControl extends JPanel {
    private int delay = 10; // Create a timer with delay 1000 ms
    private Timer timer = new Timer(delay, new TimerListener());

    private ArrayList<Ball> balls = new ArrayList<Ball>();
    private JButton jbtSuspend = new JButton("Suspend");
    private JButton jbtResume = new JButton("Resume");
    private JButton jbtAdd = new JButton("Add");
    private JButton jbtRemove = new JButton("Remove");
    private JButton jbtDirection = new JButton("Direction");
    private JScrollBar jsbDelay = new JScrollBar();
    private JPanel jplBalls = new JPanel(new BorderLayout());

    public BallControl() { // Group buttons in a panel
        JPanel panel = new JPanel();
        panel.add(jbtSuspend);
        panel.add(jbtResume);
        panel.add(jbtAdd);
        panel.add(jbtRemove);
        panel.add(jbtDirection);

        balls.add(new Ball());
        // Add panel-ball and buttons to the panel
        jsbDelay.setOrientation(JScrollBar.HORIZONTAL);
        setDelay(jsbDelay.getMaximum());
        timer.setDelay(delay);

        jplBalls.setBorder(new LineBorder(Color.blue));
        jplBalls.setVisible(true);

        setLayout(new BorderLayout());
        add(jsbDelay, BorderLayout.NORTH);
        add(jplBalls, BorderLayout.CENTER);
        add(panel, BorderLayout.SOUTH);     
        timer.start();
        jplBalls.add(balls.get(balls.size()-1));

        // Register listeners
        jbtSuspend.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                timer.stop();
            }
        });
        jbtResume.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                timer.start();
            }
        });
        jbtAdd.addActionListener(new ActionListener() {     
            public void actionPerformed(ActionEvent e) {
                addNewBallToPanel();
                if(jbtRemove.isEnabled()==false)
                    jbtRemove.setEnabled(true);
            }
        });
        jbtRemove.addActionListener(new ActionListener() {      
            public void actionPerformed(ActionEvent e) {
                removeBallFromPanel();
                if (balls.size()==0)
                    jbtRemove.setEnabled(false);
            }
        });
        jbtDirection.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                for(Ball b : balls)
                    b.changeDirection();
            }
        });

        jsbDelay.addAdjustmentListener(new AdjustmentListener() {
            public void adjustmentValueChanged(AdjustmentEvent e) {
                setDelay(jsbDelay.getMaximum() - e.getValue());
                timer.setDelay(delay);
            }
        });
    }

    public void addNewBallToPanel(){
        balls.add(new Ball());
        jplBalls.add(balls.get(balls.size()-1));

    }

    public void paintComponent(Graphics g){
        super.paintComponent(g);
        for (Ball b : balls){
            b.paintComponent(g);
        }
    }

    private class TimerListener implements ActionListener {
        /** Handle the action event */
        public void actionPerformed(ActionEvent e) {
            repaint();
        }
    }

    public void removeBallFromPanel(){
        jplBalls.remove(balls.get(balls.size()-1));
        balls.remove(balls.size()-1);
    }

    public void setDelay(int delay) {
        this.delay = delay;
    }
}

Ball:

import javax.swing.Timer;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class Ball extends JPanel {
    //private int delay = 10; // Create a timer with delay 1000 ms
    //private Timer timer = new Timer(delay, new TimerListener());
    private int x = 0;
    private int y = 0; // Current ball position
    private int radius = 5; // Ball radius
    private int dx = 2; // Increment on ball's x-coordinate
    private int dy = 2; // Increment on ball's y-coordinate
    private Color c;

    public void setC(Color c) {
        this.c = c;
    }

    public Ball() {
        c = new Color((int)(Math.random()*255), (int)(Math.random()*255), (int)(Math.random()*255));
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(c);
        if (x < radius)
            dx = Math.abs(dx); // Check boundaries
        if (x > getWidth() - radius)
            dx = -Math.abs(dx);
        if (y < radius)
            dy = Math.abs(dy);
        if (y > getHeight() - radius)
            dy = -Math.abs(dy);
        x += dx; // Adjust ball position
        y += dy;
        g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
    }

    public void changeDirection(){
        dx=-dx;
        dy=-dy;
    }
}

BounceBallApp:

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

public class BounceBallApp extends JApplet {
    public BounceBallApp() {
        add(new BallControl());
    }

    public void init(){
        this.setSize(500, 300);
    }
}

Upvotes: 0

Views: 5286

Answers (3)

Paul Samsotha
Paul Samsotha

Reputation: 209122

Your problem is that your Ball is a panel. You really only want the Ball to be a data class that holds the data of how to draw a class. The problem with your code is that you r trying to add a new ball panel when really you only want to add a ball to an existing panel. So the logic from your Ball class, you need to move that to the BallControll. Here's a running example. I tweeked your code. You can get an idea from it, what needs to be fixed.

import javax.swing.Timer;
import java.util.ArrayList;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class BounceBallApp extends JFrame {

    public BounceBallApp() {
        add(new BallControl());
    }

    public static void main(String[] args) {
        JFrame frame = new BounceBallApp();
        frame.setTitle("MultipleBallApp");
        frame.setSize(300, 200);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    class BallControl extends JPanel {

        private BallPanel ballPanel = new BallPanel();
        private JButton jbtSuspend = new JButton("Suspend");
        private JButton jbtResume = new JButton("Resume");
        private JButton jbtAdd = new JButton("Add");
        private JButton jbtSubtract = new JButton("Remove");
        private JScrollBar jsbDelay = new JScrollBar();

        public BallControl() {
            // Group buttons in a panel
            JPanel panel = new JPanel();
            panel.add(jbtSuspend);
            panel.add(jbtResume);
            panel.add(jbtAdd);
            panel.add(jbtSubtract);

            // Add ball and buttons to the panel
            ballPanel.setBorder(
                    new javax.swing.border.LineBorder(Color.red));
            jsbDelay.setOrientation(JScrollBar.HORIZONTAL);
            ballPanel.setDelay(jsbDelay.getMaximum());
            setLayout(new BorderLayout());
            add(jsbDelay, BorderLayout.NORTH);
            add(ballPanel, BorderLayout.CENTER);
            add(panel, BorderLayout.SOUTH);

            // Register listeners
            jbtSuspend.addActionListener(new Listener());
            jbtResume.addActionListener(new Listener());
            jbtAdd.addActionListener(new Listener());
            jbtSubtract.addActionListener(new Listener());
            jsbDelay.addAdjustmentListener(new AdjustmentListener() {
                @Override
                public void adjustmentValueChanged(AdjustmentEvent e) {
                    ballPanel.setDelay(jsbDelay.getMaximum() - e.getValue());
                }
            });
        }

        class Listener implements ActionListener {

            @Override
            public void actionPerformed(ActionEvent e) {
                if (e.getSource() == jbtSuspend) {
                    ballPanel.suspend();
                } else if (e.getSource() == jbtResume) {
                    ballPanel.resume();
                } else if (e.getSource() == jbtAdd) {
                    ballPanel.add();
                } else if (e.getSource() == jbtSubtract) {
                    ballPanel.subtract();
                }
            }
        }
    }

    class BallPanel extends JPanel {

        private int delay = 10;
        private ArrayList<Ball> list = new ArrayList<Ball>();

        // Create a timer with the initial delay
        protected Timer timer = new Timer(delay, new ActionListener() {
            @Override
            /**
             * Handle the action event
             */
            public void actionPerformed(ActionEvent e) {
                repaint();
            }
        });

        public BallPanel() {
            timer.start();
        }

        public void add() {
            list.add(new Ball());
        }

        public void subtract() {
            if (list.size() > 0) {
                list.remove(list.size() - 1); // Remove the last ball
            }
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            for (int i = 0; i < list.size(); i++) {
                Ball ball = (Ball) list.get(i); // Get a ball
                g.setColor(ball.color); // Set ball color

                // Check boundaries
                if (ball.x < 0 || ball.x > getWidth()) {
                    ball.dx = -ball.dx;
                }

                if (ball.y < 0 || ball.y > getHeight()) {
                    ball.dy = -ball.dy;
                }

                // Adjust ball position
                ball.x += ball.dx;
                ball.y += ball.dy;
                g.fillOval(ball.x - ball.radius, ball.y - ball.radius,
                        ball.radius * 2, ball.radius * 2);
            }

        }

        public void suspend() {
            timer.stop();
        }

        public void resume() {
            timer.start();
        }

        public void setDelay(int delay) {
            this.delay = delay;
            timer.setDelay(delay);
        }
    }

    class Ball {

        int x = 0;
        int y = 0; // Current ball position
        int dx = 2; // Increment on ball's x-coordinate
        int dy = 2; // Increment on ball's y-coordinate
        int radius = 5; // Ball radius
        Color color = new Color((int) (Math.random() * 256),
                (int) (Math.random() * 256), (int) (Math.random() * 256));
    }
}

Note: I left the direction part out. Id was confusing me. You can fix that.

Upvotes: 1

camickr
camickr

Reputation: 324207

Swing components use layout managers. You created your balls panel with a BorderLayout. A BorderLayout can only display a single component in the CENTER, which is where you are adding all your Balls.

When you add additional Balls you never revalidate() the panel, so by default all the Balls have a 0 size which means there is nothing to paint. Even if you did revalidate() the panel the only the last panel added with display since by default a panel is opaque so the last ball added would paint over top of the other Balls. So the simple answer to your question is that you probably need to make the Balls non-opaque.

However, that is still not a very good design because you will potentially be adding many Balls to the panel and each ball will be sized at roughly (500, 300). Since all the Balls are non-opaque the painting system will need to do a lot of work to find an opaque background which needs to be painted before all the Balls are painted.

If you want to deal with components. Then each component should be non-opaque and the size of each component should only be the size of the actual oval that you are painting. You would need to use a null layout so you can randomly position every ball on the panel. Then you need to set the size/location of every Ball so that it can be painted properly.

Or another approach is to not use components at all but instead to just keep information about every Ball in an ArrayList and then do custom painting that simply iterates through the ArrayList to paint each Ball.

For an example of this last approach check out the DrawOnComponent example from Custom Painting Approaches. The code is not exactly what you want, but the concept is the same except your user will be clicking a button to add a circle instead of using the mouse.

Upvotes: 1

Seagull
Seagull

Reputation: 13859

Two edits seems to fix problem:

In BallControl

private JPanel jplBalls = new JPanel(null);
jplBalls.setOpaque(false);

In Ball

if (x > getParent().getWidth() - radius)
    dx = -Math.abs(dx);

if (y > getParent().getHeight() - radius)
    dy = -Math.abs(dy);

But i should suggest you to have one panel and draw balls from array in it's paintComponent method.

Now you have created a sandwich from multiple panels and they are overlap each other.

Another way is make ball a widget, and change it's coordinates.

Upvotes: 1

Related Questions