JPAnel

时间:2017-11-18 20:42:29

标签: 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;
                }
            }
        }
    }
}

1 个答案:

答案 0 :(得分:2)

  

每个图标都会绘制到自己的JPanel中,该JPanel会在容器的单列GridLayout

中填充一行

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

  1. 您需要覆盖getPreferredSize()方法,使组件具有首选大小,以便布局管理器可以完成其工作。如果您没有指定首选尺寸,那么尺寸可能为零,因此无需绘画。

  2. 绘画方法仅适用于绘画。您不应该在绘制方法中修改组件的状态,因为您无法控制Swing重绘组件的时间。所以你需要像setPositionX(...)这样的方法来控制它应该被绘制的图像位置。然后在Thread(或Timer)中调用此方法来更改位置并在组件上调用repaint()。

  3.   

    终点线也应该涂在容器上,但它没有出现

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

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

    的方法

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

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

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

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

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

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

    不要使用ImageIcon来读取文件。使用ImageIO.read(...)读取图像。然后如上所述绘制图像。

      

    并且每个JPanel都在自己的线程上比赛。

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

    编辑:

    说完以上所有内容后,只需一次更改,您的代码就可以正常工作:

    posX = 0;
    setOpaque(false);
    

    你只需要让Racer透明化它的构造函数。

    由于这似乎是学校作业,我猜你必须遵守规则,但你真的应该理解作业的问题。

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