Search code examples
javaswingconstructorpaintcomponentgraphics2d

Java Graphics - Using constructor twice delete older graphic


I have a program that displays many elements of one class at different positions. For this I'm using the constructor of the class many times. But the problem is that only the last element is shown and not the others.

The initialization of the the frame and where the constuctors are called:

private void initComponents() {

    setExtendedState(MAXIMIZED_BOTH);
    int width = (int) getContentPane().getBounds().getWidth();
    int height = (int) getContentPane().getBounds().getHeight();
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    add(new gleis(0, 0, 0, 0));
    add(new gleis(1, 0, 1, 0));
    add(new gleis(2, 0, 2, 0));

    this.setVisible(true);
}

The class of the element called 'gleis':

public class gleis extends JPanel {

    int xposition;
    int yposition;
    int id;
    int status;

    public gleis(int xpos, int ypos, int id, int status){

        this.xposition = xpos * 11;
        this.yposition = ypos * 11 + 4;
        this.id = id;
        this.status = status;

        System.out.println(this.id);
    }

    public void paintComponent(Graphics g1) {
        super.paintComponent(g1);
        Graphics2D g = (Graphics2D) g1;
        g.setColor(Color.black);
        g.fillRect(this.xposition, this.yposition, 11, 4);

        if (this.status == 0)
            g.setColor(Color.gray);
        else if (this.status == 1)
            g.setColor(Color.red);
        else if (this.status == 2)
            g.setColor(Color.yellow);
        else 
            g.setColor(Color.gray);

        g.fillRect(this.xposition + 2, this.yposition + 1, 7, 2);


    }

}

So how can I show all elements I want to have?


Solution

  • You're likely adding your component to a JFrame's contentPane, a component that uses BorderLayout as its default layout, and for this reason the last component entered is covering all the others, since they're being added defaultwise to the BorderLayout.CENTER position.

    Based on your code and my assumption of your use case, I'd suggest changing your whole approach:

    • Use one single class that extends JPanel as your drawing JPanel, and override its paintComponent method.
    • Add this JPanel to your JFrame.
    • Change the name of your gleis class to Gleis so that it conforms with Java naming conventinons, and make it a logical class, not a GUI component class, and so don't have it override paintComponent.
    • Give Gleis a public void draw(Graphics2D g2) method.
    • Add as many Gleis objects to your drawing JPanel, and then draw them within the JPanel's paintComponent method by iterating through the ArrayList of Gleis that the drawing JPanel holds, calling draw(...) on each Gleis instance.

    For example:

    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.swing.*;
    
    public class GleisMain {
        private static void createAndShowGui() {
            GleisPainter gleisPainter = new GleisPainter();
    
            gleisPainter.addGleis(new Gleis(10, 10, 0, 0));
            gleisPainter.addGleis(new Gleis(20, 20, 1, 1));
            gleisPainter.addGleis(new Gleis(30, 30, 2, 2));
    
            // I try to avoid having classes extend JFrame and instead use JFrames when needed
            JFrame frame = new JFrame("Gleis Example");
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            frame.getContentPane().add(gleisPainter);
            frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    createAndShowGui();
                }
            });
        }
    }
    
    // a single class that extend JPanel and that draws all the Gleis objects
    class GleisPainter extends JPanel {
        // an ArrayList to hold all the Gleis objects
        private List<Gleis> gleisList = new ArrayList<>();
    
        // to allow outside classes to add Gleis objects
        public void addGleis(Gleis gleis) {
            gleisList.add(gleis);
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
    
            // loop through our gleis list and draw each instance
            Graphics2D g2 = (Graphics2D) g;
            for (Gleis gleis : gleisList) {
                gleis.draw(g2);  // by calling its draw(...) method
            }
        }
    }
    
    // don't have this extend JPanel
    class Gleis {
        // fields should be private
        private int xposition;
        private int yposition;
        private int id;
        private int status;
    
        public Gleis(int xpos, int ypos, int id, int status) {    
            this.xposition = xpos * 11;
            this.yposition = ypos * 11 + 4;
            this.id = id;
            this.status = status;
    
            System.out.println(this.id);
        }
    
        // give this a public draw method where each instance can draw itself
        public void draw(Graphics2D g2) {
            g2.setColor(Color.black);
    
            // you'll want to avoid "magic" numbers like you're using here:
            g2.fillRect(this.xposition, this.yposition, 11, 4);
    
            if (this.status == 0)
                g2.setColor(Color.gray);
            else if (this.status == 1)
                g2.setColor(Color.red);
            else if (this.status == 2)
                g2.setColor(Color.yellow);
            else
                g2.setColor(Color.gray);
    
            // ditto about use of "magic" numbers
            g2.fillRect(this.xposition + 2, this.yposition + 1, 7, 2);
        }    
    }