【问题标题】:Painting a Runnable JPanel绘制一个可运行的 JPanel
【发布时间】:2013-08-21 16:37:13
【问题描述】:

我正在开发这个小赛马模拟器,并且一直坚持下去。我希望用户首先选择比赛中的马匹数量(2-6),然后单击“开始”按钮。然后,我想绘制/绘制赛道和马(用圆圈表示)。出于某种原因,当代码达到创建 Horse 实例的程度时,它永远不会被绘制到框架中。 下面是代码。我错过了什么?

Main.java:

import javax.swing.SwingUtilities;

public class Main {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {     
            @Override
            public void run() {
                RaceTrack myRace = new RaceTrack();
                myRace.setVisible(true);
            }
        });
    }
}

RaceTrack.java:

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.border.Border;

public class RaceTrack extends JFrame implements Runnable {
    public RaceTrack() {
        initUI();
    }
    public static int selectedRaceSize = 2;
    private void initUI() {
        final Container pane = getContentPane();
        String horseNum[] = { "2", "3", "4", "5", "6" };
        JPanel buttonPanel = new JPanel();
        Border border = BorderFactory.createTitledBorder("Please select number of horses:");
        buttonPanel.setBorder(border);
        ButtonGroup buttonGroup = new ButtonGroup();
        JRadioButton aRadioButton;
        //   For each String passed in:
        //   Create button, add to panel, and add to group
        for (int i = 0, n = horseNum.length; i < n; i++) {
            if (i == 0) {
                // Default selection
                aRadioButton = new JRadioButton(horseNum[i], true);
            } else {
                aRadioButton = new JRadioButton(horseNum[i]);
            }
            buttonPanel.add(aRadioButton);
            buttonGroup.add(aRadioButton);
        }

        pane.add(buttonPanel, BorderLayout.PAGE_START);
        final JPanel raceTrackPanel = new JPanel(null);
        final JButton startButton = new JButton("Start!");
        startButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                startButton.setEnabled(false);
                Horse horse1 = new Horse("horse1");
                raceTrackPanel.add(horse1);
                pane.add(raceTrackPanel, BorderLayout.CENTER);
                repaint();  
            }
        });
        pane.add(startButton, BorderLayout.PAGE_END);
        startButton.setBounds(50, 200, 300, 30);

        setTitle("Horse Race v1.0");
        setSize(400, 300);
        setResizable(false);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    @Override
    public void run() {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            repaint();
    }
}

Horse.java:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Horse extends JPanel implements Runnable {
    Thread runner;
    public Horse() {
    }
    public Horse(String threadName) {
        runner = new Thread(this, threadName);
        runner.start();
    }
    public void run() {
        this.repaint();
    }
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(new Color(252, 211, 61));
        g2d.drawOval(20, 25, 10, 10);
        g2d.fillOval(20, 25, 10, 10);
    }
}

【问题讨论】:

    标签: java multithreading user-interface paintcomponent repaint


    【解决方案1】:

    我错过了什么?

    您缺少数据模型。您正在尝试在视图中执行所有操作。

    视图用于显示模型中的数据。

    你的 Horse 类应该看起来更像这样:

    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.Point;
    import java.util.Random;
    
    public class Horse {
    
        public static final int RADIUS = 15;
        public static final int MARGIN = 15;
        public static final int DIAMETER = RADIUS + RADIUS;
        public static final int POSITION = DIAMETER + MARGIN;
    
        private static Point currentPosition;
    
        static {
            int x = MARGIN + RADIUS;
            int y = MARGIN + RADIUS;
            currentPosition = new Point(x, y);
        }
    
        private static Random random = new Random();
    
    
        /** Distance in pixels */
        private double distance;
    
        /** Velocity in pixels per second */
        private int velocity;
    
        private Color color;
    
        /** Initial position in pixels */
        private Point initialPosition;
    
        private String name;
    
        public Horse(Color color, String name) {
            setInitialPosition();
            this.color = color;
            this.name = name;
            init();
        }
    
        private void setInitialPosition() {
            this.initialPosition = 
                    new Point(currentPosition.x, currentPosition.y);
            currentPosition.y += POSITION;
        }
    
        public void init() {
            this.distance = 0.0D;
        }
    
        public void setVelocity() {
            this.velocity = random.nextInt(5) + 6;
        }
    
        public double getDistance() {
            return distance;
        }
    
        public String getName() {
            return name;
        }
    
        public void moveHorse(int milliseconds) {
            double pixels = 0.001D * velocity * milliseconds;
            this.distance += pixels;
        }
    
        public void draw(Graphics g) {
            g.setColor(color);
            g.fillOval(initialPosition.x + (int) Math.round(distance) - RADIUS,
                    initialPosition.y - RADIUS, DIAMETER, DIAMETER);
        }
    
    }
    

    类中的最后一个方法是draw。在创建动画时,如果对象自己绘制会容易得多。

    这是一个 Race 课程。

    import java.awt.Color;
    import java.awt.Graphics;
    import java.util.ArrayList;
    import java.util.List;
    
    public class Race {
    
        /** Distance of race in pixels */
        private double      distance;
    
        private long        elapsedTime;
    
        private List<Horse> horses;
    
        public Race(double distance) {
            this.distance = distance;
            this.horses = new ArrayList<Horse>();
            this.elapsedTime = 0;
        }
    
        public void init() {
            this.elapsedTime = 0;
            for (Horse horse : horses) {
                horse.init();
            }
        }
    
        public void addHorse(Horse horse) {
            this.horses.add(horse);
        }
    
        public int getHorseCount() {
            return horses.size();
        }
    
        public double getDistance() {
            return distance;
        }
    
        public void setElapsedTime(long elapsedTime) {
            if (isWinner() == null) {
                this.elapsedTime = elapsedTime;
            }
        }
    
        public String getElapsedTime() {
            int centiseconds = (int) (((elapsedTime % 1000L) + 5L) / 10L);
            int seconds = (int) (elapsedTime / 1000L);
            if (seconds < 60) {
                return String.format("%2d.%02d", seconds, centiseconds);
            } else {
                int minutes = seconds / 60;
                seconds -= minutes * 60;
                return String.format("%2d:%02d.%02d", minutes, seconds,
                        centiseconds);
            }
        }
    
        public int getTrackWidth() {
            return (int) Math.round(getDistance()) + 100;
        }
    
        public int getTrackHeight() {
            return getHorseCount() * Horse.POSITION + Horse.MARGIN;
        }
    
        public void setHorseVelocity() {
            for (Horse horse : horses) {
                horse.setVelocity();
            }
        }
    
        public void updateHorsePositions(int milliseconds) {
            for (Horse horse : horses) {
                horse.moveHorse(milliseconds);
            }
        }
    
        public Horse isWinner() {
            for (Horse horse : horses) {
                if ((distance - Horse.RADIUS) <= horse.getDistance()) {
                    return horse;
                }
            }
    
            return null;
        }
    
        public boolean allHorsesRunning() {
            for (Horse horse : horses) {
                if ((distance + Horse.RADIUS + 6) > horse.getDistance()) {
                    return true;
                }
            }
    
            return false;
        }
    
        public void draw(Graphics g) {
            drawLine(g, Horse.POSITION, 6);
            drawLine(g, (int) Math.round(getDistance()) + Horse.RADIUS
                    + Horse.MARGIN, 6);
    
            for (Horse horse : horses) {
                horse.draw(g);
            }
        }
    
        private void drawLine(Graphics g, int x, int width) {
            int y = Horse.MARGIN;
            int height = getHorseCount() * Horse.POSITION - y;
            g.setColor(Color.BLACK);
            g.fillRect(x, y, width, height);
        }
    
    }
    

    同样,draw 方法绘制了比赛。

    那么,您实际绘制的 JPanel 会是什么样子?

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    
    import javax.swing.JPanel;
    
    import com.ggl.horse.race.model.Race;
    
    public class RacePanel extends JPanel {
    
        private static final long   serialVersionUID    = 1040577191811714944L;
    
        private Race race;
    
        public RacePanel(Race race) {
            this.race = race;
            int width = race.getTrackWidth();
            int height = race.getTrackHeight();
            this.setPreferredSize(new Dimension(width, height));
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            drawBackground(g);
            race.draw(g);
        }
    
        private void drawBackground(Graphics g) {
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    
    }
    

    视图不关心是一匹马、三匹马还是十匹马。视图没有改变。

    只有模型会改变。

    这应该足以帮助您入门。

    【讨论】:

    • 我已根据您的建议重写了我的整个代码。我从未了解过数据模型与视图,所以我仍在试图了解这里发生了什么。但是有一个问题:Race 类也可以作为我的框架吗?如果没有,我应该为它使用不同的类还是只在我的 Main 类中创建它?
    • @coyote1982:您的 JFrame 存在用于容纳 TrackPanel 和菜单。您的 JFrame 是视图的一部分。种族是模型的一部分。您需要编写控制器类来将模型和视图绑定在一起(一个或多个线程)。我在你的代码中看到了线程,所以我认为你可以做到这一点。查看这篇文章,了解如何将 Swing GUI 组合在一起。 java-articles.info/articles/?p=196
    • 感谢您的精彩指导,我在过去几天取得了很大进步。我的程序快完成了。我仍然需要编写比赛结果对话框,但在开始之前,我遇到了一个问题:我无法找出真正让马移动的正确方法。每匹马都有自己的 HorseRunnable 来模拟它的随机进程。我还创建了一个 RaceRunnable,它每 50 毫秒重新绘制一次 TrackPanel(通过休眠,然后重新绘制)。我可以把程序发给你,以便我更好地解释吗?
    • 更新:我已经设法让事情顺利进行……所以请忽略我之前的求助。你的导游还是很棒的。如果您想使用我的程序作为多线程 GUI 新指南的基础,我很乐意与您分享。我还没有完成,所以如果我再次陷入困境,我希望你能帮助我......
    • @coyote1982:有趣的是你应该提到这一点。本周早些时候,我编写了一个赛马 GUI,只是为了说明应如何在 Swing 中编写 Java 2D 动画。文章将于9月10日发表。 java-articles.info/articles我很高兴能帮到你。
    【解决方案2】:

    为了更好的设计,不要为您的应用程序使用空布局。使用框架的默认 BorderLayout。

    1. 使用所有单选按钮创建一个 JPanel,并将该面板添加到框架的 PAGE_START。
    2. 将您的 JButton 添加到 PAGE_END
    3. 创建一个 RaceTrackPane 并将您的马匹添加到此面板。此面板可以使用空布局,因为您将移动马匹。此面板已添加到 CENTER。

    您的代码的问题在于您如何定义组件的边界并进行自定义绘制:

    horse1.setBounds(20, 120, 20, 20);
    ...
    g2d.drawOval(20, 25, 10, 10);
    g2d.fillOval(20, 25, 10, 10);
    

    第一个问题是你所有的马都定位在 (20, 120),所以它们会被画在彼此的顶部。

    更大的问题是每匹马的大小是 (20, 20)。当您进行绘画时,您在 (20, 25) 处绘制马,使其超出组件的大小。尝试使用 (0, 0, 10, 10)。也就是说,您应该始终相对于组件的 (0, 0) 进行绘画。然后通过更改组件的位置来移动组件。

    我会考虑使用带有图标的 JLabel 作为您的 Horse 组件,这样您就不必进行自定义绘画并担心所有这些。如需更高级(但可能更灵活)的解决方案,请查看Playing With Shapes

    【讨论】:

    • 感谢您的回答!目前我只是想让这匹马出现在我的框架中,然后再处理其他马。我已经根据您的建议更正了我的 RaceTrack.java,但马仍然没有出现在框架中。我已经编辑了我的原始问题以包含新代码...对不起,我不熟悉将线程与图形结合起来,我不确定我做错了什么...
    猜你喜欢
    • 1970-01-01
    • 2013-04-16
    • 2012-11-19
    • 2012-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-01
    • 1970-01-01
    相关资源
    最近更新 更多