RoiZentner
RoiZentner

Reputation: 163

Painting a Runnable JPanel

I am working on this little Horse race simulator and am stuck with it. I want the user to first select the number of horses in the race (2-6) and click on the "Start" button. Then, I want to draw/paint the race track and horses (represented by circles). For some reason, when the code reaches the point of creating an instance of a Horse, it never gets drawn into the frame. Below is the code. What am I missing?

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);
    }
}

Upvotes: 2

Views: 5680

Answers (2)

Gilbert Le Blanc
Gilbert Le Blanc

Reputation: 51445

What am I missing?

You're missing a data model. You're trying to do everything in the view.

The view is for displaying data from the model.

Your Horse class should look more like this:

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);
    }

}

The last method in the class is draw. When creating an animation, it's a lot easier if objects draw themselves.

Here's a Race class.

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);
    }

}

Again, the draw method draws the race.

So, what would the JPanel that you actually draw on look like?

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());
    }

}

The view doesn't care if there's one horse, three horses, or 10 horses. The view doesn't change.

Only the model changes.

This should be enough information to get you started.

Upvotes: 3

camickr
camickr

Reputation: 324118

For a better design, don't use a null layout for your application. Use the default BorderLayout of the frame.

  1. Create a JPanel with all your radio buttons and add that panel to the PAGE_START of your frame.
  2. Add you JButton to the PAGE_END
  3. Create a RaceTrackPane and add your Horses to this panel. This panel can use a null layout because you will be moving the horses. This panel is added to the CENTER.

The problem with your code is how you define the bounds of the component and do the custom painting:

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

First problem is that all you horses are positioned at (20, 120) so they will be painted on top of one another.

The bigger problem is the size of every horse is (20, 20). When you do your painting you paint the horse at (20, 25) so it is outside the size of your component. Try using (0, 0, 10, 10). That is you should always do your painting relative to (0, 0) of the component. You then move the component by changing the location of the component.

I would consider using a JLabel with an Icon as your Horse component so you don't have to do custom painting and worry about all this. For a little more advanced (but potentially more flexible) solution, check out Playing With Shapes.

Upvotes: 6

Related Questions