Switching JFrame's contentPane
Hi folks,
I've had a problem trying to switch the contentPane of a JFrame. Basically, I'd created a class to manage my application, that at construction time sets up the application's main window ( JFrame mainWindow ), and then have a method to switch the contentPane
publicvoid switchContentPane( JPanel newContent )throws Exception{
newContent.setOpaque(true );
mainWindow.setContentPane( newContent );
}
The problem was that every time the switchContentPane() were invoked, the GUI would be updated only if the window were resized. I've tried many things to solve this problem:
- call mainWindow.repaint() after setContentPane();
- call mainWindow.revalidate() after setContentPane();
- invoke switchContentPane using SwingUtilities.invokeLater();
None of this worked for me. The same behavior was ocurring. What worked for me was to invoke mainWindow.getRootPane().updateUI()
after mainWindow.setContentPane(). This solution was just a guessing. I don't have the minor idea of why this works, adn would like to know if someone could explain this. I would really appreciate it.
Thanks,
Alexandre
# 1
Instead of switching the content pane of the frame, try just adding and removing components from the JPanel which is the default content pane.
So, to switch your content:
public void switchContentPane(JPanel newContent) throws Exception {
newContent.setOpaque(true);
mainWindow.getContentPane().removeAll();
mainWindow.getContentPane().add(newContent);
}
Removing and adding things should be properly invalidating the necessary containers to get them to be repainted.
I haven't tried this, and I'm no Swing expert, but I haven't seen content panes being switched like that before.
# 2
Don't switch the content pane.Use a CardLayout instead. http://java.sun.com/docs/books/tutorial/uiswing/layout/card.html
# 3
I've tried many things to solve this problem:
- call mainWindow.repaint() after setContentPane();
- call mainWindow.revalidate() after setContentPane();
- invoke switchContentPane using SwingUtilities.invokeLater();
The solution is to call validate() and then repaint(), as with any time when you modify an on-screen component hierarchy. The code below works perfectly.
JFrame doesn't even have a revalidate() method so I'm not sure how you managed to try that one.
I haven't tried this, and I'm no Swing expert, but I haven't seen content panes being switched like that before.
It's a perfectly decent way of doing it, and I would suggest a better way - why have an arbitrarily-configured panel sitting there doing nothing except ruining the semantics of placing your content into a frame?
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestCP implements Runnable
{
private JFrame frame = null;
private int swaps = 0;
public static void main(String[] args)
{
SwingUtilities.invokeLater(new TestCP());
}
public void run()
{
this.frame = new JFrame();
updateContent();
this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frame.pack();
this.frame.setLocationRelativeTo(null);
this.frame.setVisible(true);
}
private void updateContent()
{
JLabel l = new JLabel("Swaps: " + this.swaps, JLabel.CENTER);
JButton b = new JButton(new Swap());
JPanel p = new JPanel(new BorderLayout());
p.add(l, BorderLayout.CENTER);
p.add(b, BorderLayout.SOUTH);
this.frame.setContentPane(p);
this.frame.validate();
this.frame.repaint();
}
private class Swap extends AbstractAction
{
private Swap()
{
putValue(NAME, "Swap");
}
public void actionPerformed(ActionEvent e)
{
swaps ++;
updateContent();
}
}
}
# 4
Thanks all for your tips.
Sorry for my mistake about calling revalidate() on JFrame.
About the suggestion of removing and adding things, I've seen this before, but I think it's not an elegant solution, since the container in the contentPane would be just another container into the containment hierarchy, with no utility.
About the solution given by Itchyscratchy, I think I didn't get a good understanding from JFrame's API. I thought that calling only validate() or only repaint() would be enough to update the GUI. Never thought about invoking both together. I think that the UpdateUI() rootPane's method does exactly what the validate() / repaint() does. I'll check te source code to discover the details.
Thanks all,
Alexandre
# 5
validate() ensures that the hierarchy is up to date.
repaint() schedules a repaint request for a component.
You should call repaint() when a component needs to be repainted but when it hasn't had any components added or removed from it, and both when it has.
Check the Javadoc for java.awt.Component - no need to go rooting in the source code.
# 6
> I think that the UpdateUI() rootPane's method does exactly what the validate() / repaint() does
No. Using updateUI is wrong. The updateUI method is used when a LAF change is made. So you have no need to invoke the method manually.
On a Swing component you should be using revalidate(). This will basically cause the LayoutManager to be invoked and the components layed out correctly. The component will be painted automatically. The default content pane is a JPanel so you would need to get the content pane first and cast it to a JPanel before using revalidate(). (Note: in most cases using validate will work as well, although I have found a few cases where it doesn't work. Of course I can't remember the exact situation).
Also note that revalidate() doesn't work all the time either. The one case I can think of is when you remove a component and then add back a component of the exact same size. In this case the LayoutManager doesn't think anything has changed and therefore doesn't tell the container to repaint itslef. In this cause you need to invoke repaint().
These are my personal experiences.
