Search code examples
javaswingjpanelpaintcomponent

Can not draw using paintComponent if added to Jpanel but works fine in JFrame


I want to paint simple oval. In the following code, if I add draw a oval on Jpanel then it doesn't work however it works fine if I draw on frame. Following is the code which works.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;

public class Controller {

    public Controller() {
        View view = new View();
        Model model = new Model(100, 100, 100, 100);
        view.paintModel(model);
    }

    public static void main(String[] args) {
        Controller c = new Controller();
    }

    public class View extends JFrame {
        private JPanel jpanel;

        public View() {
            this.setBounds(0, 0, 500, 500);
            this.setLayout(new BorderLayout());

            jpanel = new JPanel();
            jpanel.setBackground(Color.WHITE);
            jpanel.setLayout(null);
            this.add(jpanel, BorderLayout.CENTER);

            this.setVisible(true);
            this.validate();
        }

        public void paintModel(Model model) {
            jpanel.add(new ModelView(model));
            jpanel.validate();
            jpanel.repaint();
        }

    }

    public class Model {
        public int xPos;
        public int yPos;
        public int height;
        public int width;

        public Model(int xPos, int yPos, int height, int width) {
            super();
            this.xPos = xPos;
            this.yPos = yPos;
            this.height = height;
            this.width = width;
        }
    }

    public class ModelView extends JComponent {

        private Model model;

        public ModelView(Model model) {
            super();
            setBorder(new LineBorder(Color.RED));
            this.model = model;
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(model.xPos + model.width, model.yPos
                    + model.height);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.black);
            g.fillOval(model.xPos, model.yPos, model.width, model.height);
        }
    }

}

Solution

  • First of all, you are not calling super.paintComponent in you ModelView class, this is incredibly important, especially when dealing with transparent components like JComponent

    Secondly, you've provide no layout information of the ModelView, that is, the component has no preferred, minimum or maximum size hint, so many of the layout managers will consider it to have a size of 0x0, which is what is happening here.

    JPanel by default uses FlowLayout which will use the components preferred size to lay it out.

    Update the ModelView to provide some sizing hints (in most cases, overriding getPreferredSize should be sufficient) or use a different layout manager on the JPanel you are adding the ModelView to which doesn't care about such things (like BorderLayout for example)

    Updated

    Additionally, you "may" be painting beyond the visual bounds of the component, for example, you create a model using, Model model = new Model(100, 100, 100, 100) then you render that model using g.fillOval(model.xPos, model.yPos, model.width, model.height), but the preferredSize of your component is only new Dimension(model.width, model.height), you are actually painting your oval at the right, lower edge (outside) of the component

    Unless you intend to write you own layout manager to manage this, you should be taking the x/y offset into account when calculating the preferred size of the component, for example, new Dimension(model.xPos + model.width, model.yPos + model.height)

    For example...sorry, I butchered your code a little to seed the model...

    enter image description here

    The red line is just a LineBorder which shows the boundaries of the ModelView component

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.border.LineBorder;
    
    public class View extends JFrame {
    
        private JPanel jpanel;
    
        public static void main(String[] args) {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    }
    
                    new View();
    
                }
            });
        }
    
        public View() {
            this.setBounds(0, 0, 500, 500);
            this.setLayout(new BorderLayout());
    
            jpanel = new JPanel();
            jpanel.setBackground(Color.WHITE);
            this.add(jpanel, BorderLayout.CENTER);
    
            Model model = new Model(100, 100, 100, 100);
            paintModel(model);
    
            this.setVisible(true);
        }
    
        public void paintModel(Model model) {
            jpanel.add(new ModelView(model));
            jpanel.validate();
            jpanel.repaint();
        }
    
        public class ModelView extends JComponent {
    
            private Model model;
    
            public ModelView(Model model) {
                super();
                setBorder(new LineBorder(Color.RED));
                this.model = model;
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(model.xPos + model.width, model.yPos + model.height);
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                g.setColor(Color.black);
                g.fillOval(model.xPos, model.yPos, model.width, model.height);
            }
        }
    
        public class Model {
    
            public int xPos;
            public int yPos;
            public int height;
            public int width;
    
            public Model(int xPos, int yPos, int height, int width) {
                super();
                this.xPos = xPos;
                this.yPos = yPos;
                this.height = height;
                this.width = width;
            }
        }
    }