java - 线条未绘制到 JPanel

标签 java multithreading swing paintcomponent

这是一个动画,其中给定数量的图标从框架左侧开始,然后穿过屏幕到达右侧。每个图标都绘制到其自己的 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() 方法中进行自定义绘制的一般规则的异常(exception))。如果您阅读我提供的教程链接,您将看到使用此方法将导致在绘制所有 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.

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

编辑:

话虽如此,您的代码只需进行一次更改即可正常工作:

posX = 0;
setOpaque(false);

您只需在 Racer 的构造函数中将其设置为透明即可。

由于这似乎是一项学校作业,我想你必须遵守规则,但你确实应该了解作业的问题。

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

关于java - 线条未绘制到 JPanel,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47370820/

相关文章:

java - 获取从中调用方法以在 Java 中进行日志记录的类的名称

java - 我应该用同步方法改变字段吗?

java在查询后设置id

java - SwingWorker 和 SwingUtilities.invokeLater 的区别

java - 如何在java中重新启动线程?

java - 无法调整 BoxLayout 中 JLabels 的大小

java - Nimbus TableHeader 未突出显示为 'pressed'

java - 使用 TextField<Integer> 时 Wicket 中的范围验证

未找到 JavaFX Controller 方法

java - 如何复制(而不是引用)二维字符串?