简体   繁体   English

如何在一个JPanel上绘制多个相同类的对象?

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

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 had any lab time and this assignment is due on Monday! 通常,我只是去实验室时间问电讯局长,但他整周都病了,所以我们没有任何实验室时间,这个作业要在星期一完成!

the specific problem I have is related to creating a java application that displays a frame with a button allowing the user to create a ball that starts bouncing on the screen and rebounds off the frame boundaries. 我遇到的特定问题与创建一个Java应用程序有关,该应用程序显示带有按钮的框架,允许用户创建一个球,该球开始在屏幕上弹起并反弹出框架边界。

One of the exercises from a previous assignment was to create a similar program but that when run, would immediately display ONE ball bouncing. 上一个作业的练习之一是创建一个类似的程序,但是运行该程序时,将立即显示一个球反弹。 (which I got to work) Now, we have to modify our code and incorporate the button which allows us to create multiple balls. (我必须开始工作)现在,我们必须修改代码并加入允许我们创建多个球的按钮。

At first, I thought this would be an easy modification, but now I am getting confused about how to actually instantiated the Ball objects. 起初,我认为这将是一个简单的修改,但是现在我对如何实际实例化Ball对象感到困惑。 My thought process is that I first have to make the ReboundPanel and button panel appear (which works), and then whenever the user presses the button, a new Ball object is instantiated and displayed onto the ReboundPanel. 我的思考过程是,我首先必须使ReboundPanel和按钮面板出现(起作用),然后每当用户按下按钮时,都会实例化一个新的Ball对象并将其显示在ReboundPanel上。 (which does not work at present) (目前不起作用)

thanks to all for the help! 感谢大家的帮助!

Main program: 主程序:

import java.awt.*;

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));

    JPanel reboundPanel = new ReboundPanel();
    JPanel buttonPanel = new ButtonPanel();
    frame.getContentPane().add(reboundPanel);
    frame.getContentPane().add(buttonPanel);
    frame.pack();
    frame.setVisible(true);
}
}

Panel where ball should appear: 应出现球的面板:

import java.awt.*;

public class ReboundPanel extends JPanel {

private final int WIDTH = 400, HEIGHT = 300;

public ReboundPanel() {

    setPreferredSize (new Dimension(WIDTH, HEIGHT));
    setBackground (Color.black);

}
}

Button Panel: 按钮面板:

import java.awt.*;

public class ButtonPanel extends JPanel {

private final int WIDTH = 400, HEIGHT = 35;

public ButtonPanel() {

    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) {

            new Ball(); 

        }
    });
}
}

Ball Class: 球类:

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

public class Ball extends JPanel {

private final int DELAY = 20, IMAGE_SIZE = 35;

private ImageIcon image;
private Timer timer;
private int x, y, moveX, moveY;


public Ball() {

    timer = new Timer(DELAY, new ReboundListener());
    image = new ImageIcon ("/src/pa1/images/earth.gif");
    x = 0;
    y = 40;
    moveX = moveY = 3;
    draw(null);
    timer.start();
}

public void draw(Graphics page) {

    super.paintComponent (page);
    image.paintIcon (new ReboundPanel(), page, x, y);
}

private class ReboundListener implements ActionListener {

    public void actionPerformed (ActionEvent event) {

        x += moveX;
        y += moveY;

        if (x <= 0 || x >= WIDTH-IMAGE_SIZE)
            moveX = moveX * -1;

        if (y <= 0 || x >= WIDTH-IMAGE_SIZE)
            moveY = moveY * -1;

        repaint();
    }
}
}

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. 该应用程序将具有一个窗口,其中包含一个按钮和一个容纳0个或多个球的容器区域。 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]. 我们有一些动词:(具有,包含,保持,添加),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. 让我们创建一个代表窗口的类并将其称为Rebound,一个代表容器的类称为BallPanel,另一个代表球的类称为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);
    }
}

I made the ReboundPanel responsible for telling the balls to move and paint – it has a timer and an ArrayList of all the balls. 我让ReboundPanel负责告诉球移动和绘画-它有一个计时器和所有球的ArrayList。 I marked the changes to Rebound.java and ButtonPanel.java. 我标记了对Rebound.java和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 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 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 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);
}
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在一个JPanel上实现多个对象 - How to implement multiple objects onto one JPanel 如何将一个JPanel的特定坐标绘制到另一个JPanel上 - How to paint specific coordinates of one JPanel onto another JPanel 如何将同一类中的2D图形和图像分别绘制到同一JPanel - How to paint 2Dgraphics and images separately in the same class to the same JPanel 如何删除一个JPanel并在同一JFrame中绘制一个新的? - how to delete a JPanel and paint a new one in the same JFrame? 尝试在JPanel上绘制图像 - Trying to paint an Image onto a JPanel 如何在Java中不使用Jpanel的情况下直接将文本绘制到JFrame上? - How to paint text directly onto a JFrame without using a Jpanel in Java? 如何将JLabel从JFrame中的一个JPanel拖动到同一JFrame中另一个JPanel的JTextField中? - How should I drag a JLabel from one JPanel in a JFrame onto a JTextField in another JPanel in the same JFrame? 将一个 JPanel 放到另一个来自不同 Java 类的 JPanel 上 - Put one JPanel onto another JPanel from a different Java class 如果在同一个框架中有多个Java,我如何在特定的JPanel中绘制 - How can I paint in an specific JPanel when more than one in same frame- Java 有没有一种方法可以从异步方法绘制到JPanel上? - Is there a way to paint onto a JPanel from an async method?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM