How To Paint Multiple Objects Of Same Class Onto One JPanel?

前端 未结 2 1880
轻奢々
轻奢々 2020-12-21 21:27

I am a university student and I\'m having trouble with my assignment. Normally, I would just go to the lab time and ask the TA, but he\'s been sick all week so we haven\'t h

相关标签:
2条回答
  • 2020-12-21 22:23

    Think about the relationship of the elements of your application and let that help form your class and object design.

    Description:

    The application will have a window that contains a button and a container area to hold 0 or more balls. When the button is clicked, a new ball should be added to the container. A ball should move around its container at a fixed speed and bounce off the boundaries of the container.

    Design:

    This description tells us a lot about how we could structure our code. We have some nouns: (application), window, button, container, boundaries and ball. We have some verbs: (have, contains, hold, add), move[ball], bounce[ball], click[button].

    The nouns hint at possible classes to implement. And the verbs at possible methods to be implemented in the associated classes.

    Let's create a class to represent the window and call it Rebound, a class representing the container called BallPanel and a class representing a ball called Ball. In this context the window and the application can be considered to be the same. It turns out that the button can be implemented neatly without creating a separate class for it. And the boundaries are simple enough to be represented by integers.

    Above I have just explained one approach to help clarify the problem and below I will provide one possible implementation. These are intended to offer some instructional hints to help your understanding. There are many ways you could analyse this problem or implement the solution, I hope you find these helpful.

    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.ComponentAdapter;
    import java.awt.event.ComponentEvent;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.swing.AbstractAction;
    import javax.swing.BoxLayout;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    import javax.swing.Timer;
    
    public class Rebound extends JFrame {
        /* Milliseconds between each time balls move */
        static final int MOVE_DELAY = 20;
    
        /* The JButton for adding a new ball. An AbstractAction
         * provides a neat way to specify the label and on-click
         * code for the button inline */
        JButton addBallButton = new JButton(new AbstractAction("Add ball") {
            public void actionPerformed(ActionEvent e) {
                ballContainer.addBall();
            }
        });
    
        /* The Panel for holding the balls. It will need to
         * keep tracks of each ball, so we'll make it a subclass
         * of JPanel with extra code for the ball management (see
         * the definition, after the end of the Rebound class) */
        BallPanel ballContainer = new BallPanel();
    
        public Rebound() {
            super("Rebound");
            setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
            getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
    
            /* There was no neat way to specify the button size
             * when we declared it, so let's do that now */
            addBallButton.setPreferredSize(new Dimension(400, 35));
    
            /* Add the components to this window */
            getContentPane().add(addBallButton);
            getContentPane().add(ballContainer);
    
            pack();
    
            /* Create a timer that will send an ActionEvent
             * to our BallPanel every MOVE_DELAY milliseconds */
            new Timer(MOVE_DELAY, ballContainer).start();
        }
    
        /* The entry point for our program */
        public static void main(String[] args) {
            /* We use this utility to ensure that code
             * relating to Swing components is executed
             * on the correct thread (the Swing event 
             * dispatcher thread) */
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    new Rebound().setVisible(true);
                }
            });
        }
    }
    
    /* Our subclass of JPanel that also manages a list of
     * balls. It implements ActionListener so that it can
     * act on the Timer event we set up in the Rebound class */
    class BallPanel extends JPanel implements ActionListener {
        /* An automatically expanding list structure that can
         * contain 0 or more Ball objects. We'll create a Ball
         * class to manage the position, movement and draw code
         * for each ball. */
        List<Ball> balls = new ArrayList<Ball>();
        /* Let's add some code that will be run
         * when the panel is resized (which will happen
         * if its window is resized.) We need to make sure
         * that each Ball is told about the new bounds
         * of the component, so it knows that the place
         * where it should bounce has changed */
        public BallPanel() {
            super();
            setPreferredSize(new Dimension(400,300));
            addComponentListener(new ComponentAdapter() {
                public void componentResized(ComponentEvent e) {
                    if (BallPanel.this == e.getComponent()) {
                        for (Ball ball : balls) {
                            ball.setBounds(getWidth(), getHeight());
                        }
                    }
                }
            });
        }
    
        /* This method is part of the JPanel class we are subclassing.
         * Here we change the implementation of the method, ensuring
         * we call the original implementation so that we are only
         * adding to what it does. */
        public void paintComponent(Graphics g) {
            /* Call the original implementation of this method */
            super.paintComponent(g);
    
            /* Lets draw a black border around the bounds of the component
             * to make it clear where the balls should rebound from */
            g.drawRect(0,0,getWidth(),getHeight());
    
            /* Now lets draw all the balls we currently have stored in
             * our list. */
            for (Ball ball : balls) {
                ball.draw(g);
            }
        }
        /* This method will add a new Ball into our list. Remember
         * from earlier that we call this when our button is clicked. */
        public void addBall() { 
            balls.add(new Ball(this,10,10,getWidth(),getHeight())); 
        }
        /* This method will receive the event from Timer we set up in
         * the Rebound class. We want it to cause all the ball to
         * move to their next position. */
        public void actionPerformed(ActionEvent e) {
            for(Ball ball : balls) {
                ball.move();
            }
            /* Request that Swing repaints this JPanel. This should
             * cause the paintComponent() method we implemented
             * above to be called soon after. */
            repaint();
        }
    }
    /* This is our class for keeping track of an individual ball
     * and it's position, movement and how it is drawn. */
    class Ball {
        /* Let's say all balls will have the same diameter of 35.
         * The static modifier says that this is a value
         * that is shared by all instances of Ball. */ 
        static final int SIZE = 35;
        /* Let's say all balls will have a speed in both the X and Y
         * axes of 3. The static modifier says that this is a value
         * that is shared by all instances of Ball. */ 
        static final int SPEED = 3;
        /* Each ball needs to know its position, which we will store
         * as x and y coordinates in 2D space */
        int x, y; 
        /* Each ball needs to know the bounds in which it lives, so
         * it knows when to bounce. We'll be assuming the minimum
         * bound is 0,0 in 2D space. The maximum bound will be
         * maxX,mayY in 2D space. We could have made these static
         * and shared by all balls, but that means we would have
         * to remember to change them to not be static if in the
         * future we wanted Ball to be used on more than one JPanel.
         * If we didn't remember, then we'd see some buggy behaviour. */
        int maxX, maxY;
        /* Each ball needs to know its current speed in the X and Y 
         * directions. We can use positive and negative values to
         * keep track of the direction of the ball's movement. */
        int speedX = SPEED, speedY = SPEED;
        /* Each ball needs to know which panel it is being drawn to
         * (this is needed by ImageIcon#drawImage()). */
        JPanel panel;
        public Ball(JPanel panel, int x, int y, int maxX, int maxY) { 
            this.x = x; this.y = y;
            this.maxX = maxX; this.maxY = maxY;
            this.panel = panel;
        }
        public void setBounds(int maxX, int maxY) {
            this.maxX = maxX; this.maxY = maxY;
        }
        /* This method updates the position of this ball, using
         * the current speed and bounds to work out what the new
         * position should be.
         * This should be called by our BallPanel#actionPerformed()
         * method in response to the Timer we set up in the Rebound
         * class. */
        public void move() {
            x += speedX;
            y += speedY;
            // Approx bounce, okay for small speed
            if (x<0) { speedX=-speedX; x=0; }
            if (y<0) { speedY=-speedY; y=0; }
            if (x+SIZE>maxX) { speedX=-speedX; x=maxX-SIZE; }
            if (y+SIZE>maxY) { speedY=-speedY; y=maxY-SIZE; }
        }
        /* This method is responsible for drawing this ball on
         * the provided graphics context (which should come from
         * the JPanel associated with the ball). We also have
         * the panel, should we need it (ImageIcon#drawImage() needs 
         * this, but Graphics#drawOval() does not.)
         */
        public void draw(Graphics g) {
            //image.paintIcon(panel, g, x, y); - commented out because I don't have an ImageIcon
            g.drawOval(x, y, SIZE, SIZE);
        }
    }
    
    0 讨论(0)
  • 2020-12-21 22:24

    I made the ReboundPanel responsible for telling the balls to move and paint – it has a timer and an ArrayList of all the balls. I marked the changes to Rebound.java and ButtonPanel.java. The other two changed so much there wasn't any point.

    Rebound.java

    import java.awt.*;
    import javax.swing.*;
    
    public class Rebound {
    
    public static void main(String[] args) {
    
        JFrame frame = new JFrame ("Rebound");
        frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS));
    
        ReboundPanel reboundPanel = new ReboundPanel(); /****/
        JPanel buttonPanel = new ButtonPanel(reboundPanel); /****/
        frame.getContentPane().add(reboundPanel);
        frame.getContentPane().add(buttonPanel);
        frame.pack();
        frame.setVisible(true);
    }
    }
    

    ReboundPanel.java

    import java.awt.*;
    import javax.swing.*;
    import java.awt.event.*;
    import java.util.ArrayList;
    
    public class ReboundPanel extends JPanel {
    
    private final int WIDTH = 400, HEIGHT = 300;
    private final int DELAY = 20;
    private ArrayList<Ball> balls;
    private Timer timer;
    
    public ReboundPanel() {
    
        balls = new ArrayList<Ball>();
        timer = new Timer(DELAY, new ReboundListener());
        setPreferredSize (new Dimension(WIDTH, HEIGHT));
        setBackground (Color.black);
        timer.start();
    
    }
    
    public void addBall(Ball b) {
    
        balls.add(b);
    }
    
    protected void paintComponent(Graphics g) {
    
        super.paintComponent(g);
    
        for (Ball b : balls) {
    
            b.paint(g);
        }
    }
    
    private class ReboundListener implements ActionListener {
    
        public void actionPerformed (ActionEvent event) {
    
            for (Ball b : balls) {
    
                b.move(getWidth(), getHeight());
            }
            repaint();
        }
    }
    }
    

    ButtonPanel.java

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    
    public class ButtonPanel extends JPanel {
    
    private final int WIDTH = 400, HEIGHT = 35;
    
    public ButtonPanel(final ReboundPanel panel) { /****/
    
        setPreferredSize (new Dimension(WIDTH, HEIGHT));
        setBackground (Color.GRAY);
    
        JButton button = new JButton("New ball");
        add(button);
    
        button.addActionListener(new ActionListener() {
    
            public void actionPerformed(ActionEvent event) {
    
                panel.addBall(new Ball()); /****/
    
            }
        });
    }
    }
    

    Ball.java

    import javax.swing.*;
    import java.awt.*;
    
    public class Ball {
    
    private final int IMAGE_SIZE = 15;
    private int x, y, moveX, moveY;
    private ImageIcon image;
    
    public Ball() {
    
        x = 0;
        y = 40;
        moveX = moveY = 3;
        image = new ImageIcon("/src/pa1/images/earth.gif");
    }
    
    public void move(int width, int height) {
    
        x += moveX;
        y += moveY;
    
        if (x <= 0 || x >= width - IMAGE_SIZE)
            moveX = moveX * -1;
    
        if (y <= 0 || y >= height - IMAGE_SIZE)
            moveY = moveY * -1;
    }
    public void paint(Graphics g) {
    
        image.paintIcon(null, g, x, y);
    }
    }
    
    0 讨论(0)
提交回复
热议问题