我意识到这是一个老问题,但它已经被浏览了 1000 多次。
创建 Swing GUI 时,最好使用模型/视图/控制器(MVC) 模式。这种模式使我们能够分离我们的关注点并一次专注于 GUI 的一个部分。如果有问题,那么我们很清楚问题出在代码的什么地方。
这是我创建的 GUI。
所以,让我们从模型开始。这是Box
课程。
public class Box {
private final Color color;
private final Point boxMotion;
private final Rectangle rectangle;
private final String name;
public Box(String name, Color color, Rectangle rectangle,
Point boxMotion) {
this.name = name;
this.color = color;
this.rectangle = rectangle;
this.boxMotion = boxMotion;
}
public void update(int width, int height) {
this.rectangle.x += boxMotion.x;
this.rectangle.y += boxMotion.y;
int boxWidth = rectangle.x + rectangle.width;
int boxHeight = rectangle.y + rectangle.height;
if (rectangle.x < 0) {
rectangle.x = -rectangle.x;
boxMotion.x = -boxMotion.x;
}
if (boxWidth > width) {
rectangle.x = width - rectangle.width - boxMotion.x;
boxMotion.x = -boxMotion.x;
}
if (rectangle.y < 0) {
rectangle.y = -rectangle.y;
boxMotion.y = -boxMotion.y;
}
if (boxHeight > height) {
rectangle.y = height - rectangle.height - boxMotion.y;
boxMotion.y = -boxMotion.y;
}
}
public Color getColor() {
return color;
}
public Rectangle getRectangle() {
return rectangle;
}
public String getName() {
return name;
}
}
该类Box
是一个普通的 Java 类。它具有典型的 getter 方法。没有 setter 类,因为构造函数设置了所有内部字段。
我们使用 ajava.awt.Rectangle
和 ajava.awt.Point
来分别保存盒子的大小和盒子运动的方向。这使我们不必定义六个int
字段。
通过保存Color
每个盒子的运动方向和方向,我们可以有许多不同颜色和不同方向的盒子。从技术上讲,运动方向是一个 delta X 和 delta Y 实例,但我不打算创建一个单独的类来说明这一点。Point
课已经够好了。
该update
方法是最难得到正确的。该update
方法控制盒子的运动,并检查盒子是否碰到了绘图的边缘JPanel
。该方法包含在Box
类中,因为它作用于每个框。该方法将由控制器执行。
MVC 模式并不意味着模型、视图和控制器代码驻留在不同的类中,尽管这是最理想的。只要代码的执行由模型、视图和控制器分开,代码就可以放在最有意义的地方。
接下来,我们来看看Boxes
类。
public class Boxes {
private final int drawingPanelWidth;
private final int drawingPanelHeight;
private final List<Box> boxes;
public Boxes() {
this.drawingPanelWidth = 600;
this.drawingPanelHeight = 400;
this.boxes = new ArrayList<>();
addBoxesFactory();
}
private void addBoxesFactory() {
Rectangle rectangle = new Rectangle(10, 10, 50, 50);
Point point = new Point(3, 3);
this.boxes.add(new Box("box1", Color.BLACK, rectangle, point));
rectangle = new Rectangle(100, 100, 50, 50);
point = new Point(-3, -3);
this.boxes.add(new Box("box2", Color.GREEN, rectangle, point));
}
public int getDrawingPanelWidth() {
return drawingPanelWidth;
}
public int getDrawingPanelHeight() {
return drawingPanelHeight;
}
public List<Box> getBoxes() {
return boxes;
}
}
该类Boxes
是一个普通的 Java 类。它具有典型的 getter 方法。没有 setter 类,因为构造函数设置了所有内部字段。
我们使用 aList
来保存Box
我们想要显示的尽可能多的实例。在这个类中,我们定义了两个Box
实例。我们可以定义任意数量的Box
实例。其余代码将处理我们定义的任意数量的框。
JPanel
我们在这个类中定义了绘图的宽度和高度。我们这样做是因为绘图JPanel
需要这些尺寸来调整自身的大小,并且控制器需要这些尺寸来确定Box
实例何时到达宽度或高度边缘。
接下来,我们来看看主视图类,BoxMotionGUI
类。
public class BoxMotionGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new BoxMotionGUI());
}
private Boxes boxes;
private DrawingPanel drawingPanel;
public BoxMotionGUI() {
this.boxes = new Boxes();
}
@Override
public void run() {
JFrame frame = new JFrame("Multiple Components GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(boxes);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
Timer timer = new Timer(20, new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
for (Box box : boxes.getBoxes()) {
box.update(boxes.getDrawingPanelWidth(),
boxes.getDrawingPanelHeight());
drawingPanel.repaint();
}
}
});
timer.setInitialDelay(0);
timer.start();
}
}
该类BoxMotionGUI
以对SwingUtilities
invokeLater
方法的调用开始。此方法确保 Swing 应用程序将在Event Dispatch Thread上创建 Swing 组件。一个编写良好的 Swing 程序使用并发来创建一个永远不会“冻结”的用户界面。
构造函数创建Boxes
该类的一个实例。通常,您创建模型并将模型传递给视图。视图从模型中读取,但不以任何方式更新模型。控制器类将更新模型并更新/重绘视图。
run 方法包含所有用于创建JFrame
. 必须按此顺序调用这些方法。这个顺序是我用于大多数 Swing 应用程序的顺序。
java.swing.Timer
代码组成了控制器。我把这段代码放在 view 方法中,因为它很短。如果actionPerformed
代码涉及更多,则需要一个单独的控制器类。
该actionPerformed
方法更新模型并重新绘制视图。
接下来,我们来看看DrawingPanel
类。
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private final Boxes boxes;
public DrawingPanel(Boxes boxes) {
this.boxes = boxes;
this.setPreferredSize(new Dimension(
boxes.getDrawingPanelWidth(),
boxes.getDrawingPanelHeight()));
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (Box box : boxes.getBoxes()) {
g2d.setColor(box.getColor());
Rectangle rectangle = box.getRectangle();
g2d.fillRect(rectangle.x, rectangle.y,
rectangle.width, rectangle.height);
}
}
}
该类DrawingPanel
绘制Box
实例。我们扩展 a 是JPanel
因为我们想要覆盖该paintComponent
方法。您应该扩展 Swing 组件或任何 Java 类的唯一原因是您想要覆盖一个或多个类方法。
该paintComponent
方法绘制。它不会尝试做任何其他事情。由于每次重新绘制 GUI 时都会调用此方法,因此您希望尽可能减少处理量。除了绘画之外的所有其他处理都在其他地方进行。
最后,这是完整的可运行代码。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class BoxMotionGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new BoxMotionGUI());
}
private Boxes boxes;
private DrawingPanel drawingPanel;
public BoxMotionGUI() {
this.boxes = new Boxes();
}
@Override
public void run() {
JFrame frame = new JFrame("Multiple Components GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(boxes);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
Timer timer = new Timer(20, new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
for (Box box : boxes.getBoxes()) {
box.update(boxes.getDrawingPanelWidth(),
boxes.getDrawingPanelHeight());
drawingPanel.repaint();
}
}
});
timer.setInitialDelay(0);
timer.start();
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private final Boxes boxes;
public DrawingPanel(Boxes boxes) {
this.boxes = boxes;
this.setPreferredSize(new Dimension(
boxes.getDrawingPanelWidth(),
boxes.getDrawingPanelHeight()));
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (Box box : boxes.getBoxes()) {
g2d.setColor(box.getColor());
Rectangle rectangle = box.getRectangle();
g2d.fillRect(rectangle.x, rectangle.y,
rectangle.width, rectangle.height);
}
}
}
public class Boxes {
private final int drawingPanelWidth;
private final int drawingPanelHeight;
private final List<Box> boxes;
public Boxes() {
this.drawingPanelWidth = 600;
this.drawingPanelHeight = 400;
this.boxes = new ArrayList<>();
addBoxesFactory();
}
private void addBoxesFactory() {
Rectangle rectangle = new Rectangle(10, 10, 50, 50);
Point point = new Point(3, 3);
this.boxes.add(new Box("box1", Color.BLACK, rectangle, point));
rectangle = new Rectangle(100, 100, 50, 50);
point = new Point(-3, -3);
this.boxes.add(new Box("box2", Color.GREEN, rectangle, point));
}
public int getDrawingPanelWidth() {
return drawingPanelWidth;
}
public int getDrawingPanelHeight() {
return drawingPanelHeight;
}
public List<Box> getBoxes() {
return boxes;
}
}
public class Box {
private final Color color;
private final Point boxMotion;
private final Rectangle rectangle;
private final String name;
public Box(String name, Color color, Rectangle rectangle,
Point boxMotion) {
this.name = name;
this.color = color;
this.rectangle = rectangle;
this.boxMotion = boxMotion;
}
public void update(int width, int height) {
this.rectangle.x += boxMotion.x;
this.rectangle.y += boxMotion.y;
int boxWidth = rectangle.x + rectangle.width;
int boxHeight = rectangle.y + rectangle.height;
if (rectangle.x < 0) {
rectangle.x = -rectangle.x;
boxMotion.x = -boxMotion.x;
}
if (boxWidth > width) {
rectangle.x = width - rectangle.width - boxMotion.x;
boxMotion.x = -boxMotion.x;
}
if (rectangle.y < 0) {
rectangle.y = -rectangle.y;
boxMotion.y = -boxMotion.y;
}
if (boxHeight > height) {
rectangle.y = height - rectangle.height - boxMotion.y;
boxMotion.y = -boxMotion.y;
}
}
public Color getColor() {
return color;
}
public Rectangle getRectangle() {
return rectangle;
}
public String getName() {
return name;
}
}
}