21

I subclass JPanel to overwrite paintComponent(Graphics), I want to draw an image onto jpanel in a jframe.

But my image hasn't shown up until I make a change to jframe's size. This is my code:

public class ImagePanel extends JPanel{

    public void setImage(BufferedImage bi)
    {
        image = bi;
        revalidate();
    }

    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        if(image != null)
        {
            g.drawImage(image, 0, 0, this);
        }
    }
}
John
  • 3,769
  • 6
  • 30
  • 49
nautilusvn
  • 655
  • 2
  • 10
  • 20

5 Answers5

22

Verify that you invoke setVisible() after adding components and calling pack(), as discussed in this related example. You may also need to adopt an appropriate layout. Invoking repaint(), as suggested here, may fix the symptom but not the underlying cause.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
9

Take a look at the docs for JPanel.add(), which it inherits from java.awt.Container:

Appends the specified component to the end of this container. This is a convenience method for addImpl(java.awt.Component, java.lang.Object, int). This method changes layout-related information, and therefore, invalidates the component hierarchy. If the container has already been displayed, the hierarchy must be validated thereafter in order to display the added component.

Emphasis added.

Therefore, if you modify a Container after it's already been displayed, you must call validate() in order for it to show up. Just invoking repaint() is not enough. You may have noticed that calling setVisible(true) also works; this is because it calls validate() internally.

Nateowami
  • 1,005
  • 16
  • 23
  • 1
    I get better results with `revalidate()`. `validate()` does the job, but it doesn't change dimensions to fit new components added. – Chameleon May 28 '18 at 07:12
  • @Chameleon Although it's a bit late now, i think it's because you didn't override getPreferredSize – Lorenzo Jul 01 '21 at 09:29
  • `revalidate()` works for me whereas all the others don't. Who overrides `getPreferredSize()` anyway under normal circumstances?! – Mark Jeronimus Sep 04 '22 at 10:35
8

If you want to "refresh" the JPanel then you should call repaint(), which will call your paintComponent(). This should fix your problem:

public void setImage(BufferedImage bi)
{
    image = bi;
    EventQueue.invokeLater(new Runnable()
    {
        public void run()
        {
            repaint();
        }
    });
}

Its good practice to update and change the GUI using the EDT. Heres more info on the EDT if you're interested:

How does the event dispatch thread work?

repaint doesn't need to be called from the EDT. If you're changing the GUI, such as setting text to a JLabel, it should be inside of the EDT. Heres more information on what can be called outside of the EDT (courtesy of nIcE cOw):

Safe to use Component.repaint() outside EDT?

Community
  • 1
  • 1
John
  • 3,769
  • 6
  • 30
  • 49
  • 2
    +1 for the valuable input. Just a suggestion, no need to put `repaint()` call inside EDT, since it's safe to call `repaint()` from any thread, as described [here](http://stackoverflow.com/questions/9786497/safe-to-use-component-repaint-outside-edt/9786598#9786598) – nIcE cOw Jun 17 '12 at 12:30
  • 1
    "subclasses of Swing components which have a UI delegate … should invoke `super.paintComponent()`"—[*The Paint Methods*](http://java.sun.com/products/jfc/tsc/articles/painting/index.html#callback). – trashgod Jun 17 '12 at 17:09
3

I had the same problem and fixed it by call setVisible(true); the JFrame I was using.

Example : if your JFrame does not update after using :

jframe.setContentPane(new MyContentPane());

fix it with :

jframe.setContentPane(new MyContentPane());
jframe.setVisible(true);

I know that it sounds silly to do this even though your JFrame is already visible, but that's the only way I've found so far to fix this problem (the solution proposed above didn't work for me).

Here is a complete example. Run it and then uncomment the "f.setVisible(true);" instructions in classes Panel1 and Panel2 and you'll see the difference. Don't forget the imports (Ctrl + Shift + O for automatic imports).

Main class :

public class Main {
    private static JFrame f;
    public static void main(String[] args) {
        f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setContentPane(new Panel1(f));
        f.pack();    
        f.setVisible(true);
    }
}

Panel1 class :

public class Panel1 extends JPanel{
    private JFrame f;
    public Panel1(JFrame frame) {
        f = frame;
        this.setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
        JButton b = new JButton("Panel 1");
        b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                f.setContentPane(new Panel2(f));
                // Uncomment the instruction below to fix GUI "update-on-resize-only" problem
                //f.setVisible(true);
            }
        });
        add(b);
    }
}

Panel2 class :

public class Panel2 extends JPanel{
    private JFrame f;
    public Panel2(JFrame frame) {
        f = frame;
        this.setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
        JButton b = new JButton("Panel 2");
        b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                f.setContentPane(new Panel1(f));
                // Uncomment the instruction below to fix GUI "update-on-resize-only" problem
                //f.setVisible(true);
            }
        });
        add(b);
    }
}

Hope that helps.

Regards.

user2154283
  • 291
  • 1
  • 5
  • 14
  • Perfect! Not sure why that works, but that's the only thing that solved my problem. Before that, I was doing this: Dimension size = frame.getSize(); Boolean maximized = frame.getExtendedState() == JFrame.MAXIMIZED_BOTH; frame.setSize(new Dimension((int)size.getWidth()-1,(int)size.getHeight()-1)); if (maximized) frame.setExtendedState(JFrame.MAXIMIZED_BOTH ); else frame.setSize(size); Your answer is much, much cleaner and way less hacky. Thanks! – dberm22 Jan 15 '14 at 01:44
  • 1
    @dberm22 actually, no need for hacking around (at least not in this example, didn't check the original with an image of possibly different sizes, nor extended size states which might be tricky as well): setting the contentPane is-a modification of the container hierarchy of the frame and as such requires a revalidate on the frame. – kleopatra Jan 15 '14 at 13:13
1

I also had same problem but I found a solution. Just create a jframe object on top and call jframe methods at the bottom like jf.pack(), jf.setVisible(), jf.setSize(), jf.setDefaultCloseOpetion() should be at the bottom of the all UIs added in that frame you will find it work great.

alamoot
  • 1,966
  • 7
  • 30
  • 50