未将线绘制到 JPanel

Line isn't being drawn to JPanel

这是一个动画,其中给定数量的图标从画面的左侧开始,并在屏幕上快速移动到右侧。每个图标都绘制到其自己的 JPanel 中,该 JPanel 填充容器的单列 GridLayout 中的一行,并且每个 JPanel 在其自己的线程上运行。 (必须使用线程,即使 Swing Timer 可能是更好的方法。)

终点线也应该画在容器上,但它没有显示出来。我曾尝试将 Racer 的 JPanel 不透明度设置为 false,但这不起作用。我怎样才能让其他线程允许终点线的绘画执行?

无效的代码:

gui = new JPanel() {
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawLine(finishLineXPos, 0, finishLineXPos, windowHeight);
    }
};

完整代码:

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

public class Races2 {

    private JFrame frame;
    private JPanel gui;  // to hold all components
    private int finishLineXPos;  // x-coordinate of finish line
    private Icon racerImg;
    private int racerImgWidth;
    private int racerImgHeight;
    private int numOfRacers;
    private ArrayList<Racer> racers;
    private Racer winner;
    private int windowHeight;
    private int windowWidth;

    public Races(int num) {
        numOfRacers = num;
        racerImg = new ImageIcon("races.png");
        racerImgWidth = racerImg.getIconWidth();
        racerImgHeight = racerImg.getIconHeight();
        windowHeight = racerImgHeight * numOfRacers;
        windowWidth = racerImgWidth * 20;
        finishLineXPos = racerImgWidth * 18;  // two icon widths from the right

        frame = new JFrame("Off to the Races - by Brienna Herold");
        frame.setResizable(false);  // prevents window resizing which affects painting
        gui = new JPanel() {
            @Override
            public void paintComponent(Graphics g) {
                super.paintComponent(g);
                g.drawLine(finishLineXPos, 0, finishLineXPos, windowHeight);
            }
        };
        gui.setLayout(new GridLayout(numOfRacers,1));
        gui.setPreferredSize(new Dimension(windowWidth, windowHeight));

        // Create and add racers to gui panel
        racers = new ArrayList<Racer>();
        for (int i = 0; i < numOfRacers; i++) {
            Racer racer = new Racer();
            gui.add(racer);
            racers.add(racer);
        }

        // Start racers
        for (Racer racer : racers) {
            Thread racerThread = new Thread(racer);
            racerThread.start();
        }

        frame.add(gui);
        frame.pack();
        frame.setVisible(true);
    }

    protected class Racer extends JPanel implements Runnable {
        private int lastPosX;
        private int posX;

        public Racer() {
            posX = 0;
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            racerImg.paintIcon(this, g, posX, 0);
            posX += Math.random() * 20;
        }

        @Override
        public void run() {
            // While the race has not been won yet, proceed with race
            while (winner == null) {
                repaint();
                try {
                    Thread.sleep(100);  // slows down racing a bit
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }

                // If racer passes specified x-coordinate, set it as winner
                if (posX >= finishLineXPos) {
                    System.out.println("Winner: " + this.getName());
                    winner = this;
                }
            }
        }
    }
}

Each icon is drawn to its own JPanel that fills a row within a container's single-column GridLayout

首先你需要学习如何进行自定义绘画。首先阅读 Swing 教程中关于 Custom Painting 的部分,了解一些基础知识。几个关键点:

  1. 您需要覆盖 getPreferredSize() 方法以使组件具有首选大小,以便布局管理器可以完成其工作。如果您不指定首选大小,则大小可能为零,因此没有可绘制的内容。

  2. 一种绘画方法,仅作绘画之用。永远不要在绘制方法中修改组件的状态,因为您无法控制 Swing 何时重新绘制组件。所以你需要像setPositionX(...)这样的方法来控制应该绘制的图像位置。然后在线程(或计时器)中调用此方法来更改位置并在组件上调用 repaint()。

A finish line is also supposed to get painted onto the container, but it is not showing up

好吧,您将所有 Racer 组件添加到比赛面板的顶部,这样这些组件将覆盖面板上绘制的线条。

您已获得一种使用方法 racer.setOpaque(false);

另一种方法是覆盖 paint() 方法。 (这是在 paintComponent() 方法中进行自定义绘制的一般规则的例外)。如果您阅读了我提供的教程 link,您会发现使用这种方法会导致在绘制完所有 Racer 组件后才完成比赛面板的绘制。

frame.setResizable(false);  // prevents window resizing which affects painting

没有这个必要。绘画受到影响的唯一原因是因为您正在更改绘画方法中组件的状态。请参阅我上面的评论。

racerImg.paintIcon(this, g, posX, 0);

人们通常使用 drawImage(...) 方法来绘制图像。没有理由创建一个图标只是为了绘制图像。

racerImg = new ImageIcon("races.png");

不要使用 ImageIcon 来读取文件。使用 ImageIO.read(...) 读取图像。然后按照上面描述的方式绘制图像。

and each JPanel races on its own thread.

这似乎是一个愚蠢的要求,因为它给比赛带来了另一种随机性。你已经有了为图像移动生成随机距离的逻辑,那么为什么你需要单独的线程呢。相反,您应该只有一个线程,然后遍历所有 Races 并调用上面建议的 setPositionX() 方法。然后所有种族将同时重新绘制,并有自己的随机距离变化。

编辑:

综上所述,您的代码只需更改一下就可以正常工作:

posX = 0;
setOpaque(false);

您只需要在其构造函数中使 Racer 透明即可。

因为这似乎是一项学校作业,我想你必须遵守规则,但你真的应该理解作业的问题。

正如这篇文章中的其他人所建议的那样,我仍然认为更好的方法是拥有一个可以绘制的 ArrayList 或 Racer 对象(不是组件)。在你关于这个主题的最后一个问题中,我给了你一个绘制 Ball 对象的工作示例。