Reputation: 2429
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
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
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
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