Search code examples
javaswingjpanelpaintcomponent

Unable to draw manually to JPanel


I'm trying to make an orbit simulator, and I've run into this problem. I've checked all over Stack Overflow, but I can't find the solution. I'm just trying to draw manually to a JPanel, but it's not showing up on it. I've set the layout to null, made it visible, added it to the JFrame, added the Body to the Plane, and everything you would normally do.

Here's the Body class:

package viperlordx.orbitsimulator;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;

import javax.swing.JLabel;

@SuppressWarnings("serial")
public class Body extends JLabel {
    private double mass;
    private Point location;
    private Vector velocity;
    private Color color;
    private Plane plane;
    public Body(double mass, Point location, Vector velocity, Color color) {
        this.mass = mass;
        this.location = location;
        this.velocity = velocity;
        this.color = color;
        this.setVisible(true);
        this.setBounds(location.x, location.y, 100, 100);
    }
    public void moveTick() {

        velocity.addTo(location);
    }
    public void setPlane(Plane plane) {
        this.plane = plane;
    }
    public Plane getPlane() {
        return plane;
    }
    @Override
    public void paintComponent(Graphics g) {
        System.out.println("Painting");
        super.paintComponent(g);
        if (plane != null && g != null) {
            g.setColor(color);
            g.fillOval(location.x, location.y, getWidth(), getHeight());
        }
    }
    public Point getLocation() {
        return location;
    }
    public void setLocation(Point location) {
        this.location = location;
    }
    public Vector getVelocity() {
        return velocity;
    }
    public void setVelocity(Vector vector) {
        velocity = vector;
    }
}

And the Plane class:

package viperlordx.orbitsimulator;

import java.awt.Graphics;
import java.util.HashSet;
import java.util.Set;

import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Plane extends JPanel {
    private HashSet<Body> bodies = new HashSet<Body>();
    public void addBody(Body body) {
        this.add(body);
        bodies.add(body);
    }
    public Plane() {
        this.setLayout(null);
    }
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (bodies != null) {
            for (Body body : bodies) {
                body.paintComponent(g);
            }
        }
    }
    public Set<Body> getBodies() {
        return bodies;
    }
}

Now the main class:

package viperlordx.orbitsimulator;

import java.awt.Color;
import java.awt.Point;

import javax.swing.JFrame;

public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(1000, 1000);
        frame.setLocationRelativeTo(null);
        Plane plane = new Plane();
        frame.add(plane);
        plane.setVisible(true);
        frame.setLayout(null);
        plane.setBounds(0, 0, 1000, 1000);
        plane.setBackground(Color.WHITE);
        plane.addBody(new Body(10.0, new Point(10, 10), new Vector(0, 0), Color.GREEN));
        frame.setVisible(true);
        frame.setTitle("Orbit");
        plane.repaint();
    }
}

Solution

  • You are mixing up drawing shapes on a component and adding components to a container. Your Body should not be a component, simply a class that holds the data of the object to be drawn. This data is used to draw a corresponding shape on the "target" component - the Plane in your case.

    I removed irrelevant code and changed it according to my explanation above:

    public class Main {
    
        public static void main(String[] args) {
    
            SwingUtilities.invokeLater(() -> {
    
                JFrame frame = new JFrame();
                Plane plane = new Plane();
                frame.add(plane);
                plane.setBackground(Color.WHITE);
                plane.addBody(new Body(10.0, new Point(10, 10), new Vector(0, 0), Color.GREEN));
    
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setTitle("Orbit");
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            });
        }
    }
    
    @SuppressWarnings("serial")
    class Plane extends JPanel {
    
        private HashSet<Body> bodies = new HashSet<>();
    
        public void addBody(Body body) {
    
            bodies.add(body);
        }
    
        @Override
        public void paintComponent(Graphics g) {
    
            super.paintComponent(g);
            for (Body body : bodies) {
                // Might want to call all needed accessors before getting to work.
                g.setColor(body.getColor());
                g.fillOval(body.getLocation().x, body.getLocation().y, getWidth(), getHeight());
            }
        }
    
        @Override
        public Dimension getPreferredSize() {
    
            // Calculate the size
            return new Dimension(1000, 500);
        }
    }
    
    class Body {
    
        private Point location;
        private Vector velocity;
        private Color color;
    
        public Body(double mass, Point location, Vector velocity, Color color) {
    
            this.location = location;
            this.velocity = velocity;
            this.color = color;
        }
    
        public Point getLocation() {
    
            return location;
        }
    
        public Color getColor() {
    
            return color;
        }
    }
    

    enter image description here

    Notes:

    • Start Swing on the EDT.
    • Don't set the size of a frame (what if my screen doesn't have 1000 pixels in height?), call pack() instead and make sure that components on which you draw @Override getPreferredSize() appropriately. This means you will need to perform a calculation based on the things you draw on it.
    • In general, setVisible(true) should be the last thing you do. No need to repaint after it.
    • As of Java 7, you can write HashSet<Body> bodies = new HashSet<>() without specifying the generic type in the RHS.
    • Don't use the raw type Vector. In fact, no reason to use this class at all. A Set or List will usually do better. If they hold numbers used in calculation then their generic type should be Float or Double probably.