I have overridden validate() in my JFrame so that I can manually control the size of a couple of nested JPanels (they're used in scrolling content onto the screen and no layout manager I know of lets you lay out components outside of the bounds of the parent container). This works fine when dragging the window to resize it, but when clicking the Maximize button, validate() gets called, the setPreferredSize()s get called, but the panel sizes don't update. Problem seen on XP, not seen in OSX.
public void validate() {
super.validate();
LOGGER.debug("Validate called on Frame. Resizing panel");
if (inited == true) {
Dimension size = panelLeft.getSize();
int referenceHeight = size.height;
LOGGER.info("referenceHeight is " + referenceHeight);
size = lower.getSize();
size.height = referenceHeight;
lower.setPreferredSize(size);
lower.setMinimumSize(size);
size.height = size.height * 2;
movingPanel.setPreferredSize(size);
movingPanel.setMinimumSize(size);
LOGGER.info("sizes now: panel: " + lower.getSize().height + ", scrollpane: "
+ movingPanel.getSize().height);
if (panelSlideController != null) {
panelSlideController.redraw();
LOGGER.debug("redrawing panel slide controller");
}
}
}
panelLeft is a panel which is auto-resized by its layout manager to be the full height of the frame. So its height is used as a reference.
The relevant panels are arranged:
----------------------------------
|scrollPane |
| -------------------------------- |
||movingPanel ||
|| ------------------------------ ||
|||upper |||
||| |||
||| |||
||| |||
|| ------------------------------ ||
|| ------------------------------ ||
|||lower |||
||| |||
||| |||
||| |||
|| ------------------------------ ||
| -------------------------------- |
----------------------------------
The user only sees the top half of this layout. The movingPanel JPanel is in the viewport of a JScrollPane. The goal is to keep the upper panel taking up all the visible vertical space, so is roughly the same height as the enclosing JFrame. The lower panel is ready to scroll on and is kept with the same height as upper panel. By keeping lower.height == panelLeft.height and movingPanel.height == panelLeft.heightx2, it means half of movingPanel is showing, and upper ends at the bottom of the Frame.
Like I said, works fine. When dragging the window, some example output is:
2013-06-20 23:15:41,298 [WT-EventQueue-0] DEBUG s.billing.ui.Form - Validate called on Frame. Resizing panel
2013-06-20 23:15:41,298 [WT-EventQueue-0] INFO s.billing.ui.Form - newheight is 617
2013-06-20 23:15:41,298 [WT-EventQueue-0] INFO s.billing.ui.Form - sizes now: panel: 607, scrollpane: 1214
2013-06-20 23:15:41,538 [WT-EventQueue-0] DEBUG s.billing.ui.Form - Validate called on Frame. Resizing panel
2013-06-20 23:15:41,538 [WT-EventQueue-0] INFO s.billing.ui.Form - newheight is 640
2013-06-20 23:15:41,538 [WT-EventQueue-0] INFO s.billing.ui.Form - sizes now: panel: 636, scrollpane: 1272
When maximizing the window, output is like:
2013-06-20 22:08:21,234 [WT-EventQueue-0] DEBUG s.billing.ui.Form - Validate called on Frame. Resizing panel
2013-06-20 22:08:21,234 [WT-EventQueue-0] INFO s.billing.ui.Form - newheight is 783
2013-06-20 22:08:21,234 [WT-EventQueue-0] INFO s.billing.ui.Form - sizes now: panel: 543, scrollpane: 1086
I added the setMinimumSize in there to try to be more persuasive but it didn't help.
Any thoughts very welcome
EDIT: Added SSCCE
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.ScrollPaneConstants;
import javax.swing.Timer;
import javax.swing.UIManager;
class Frame extends JFrame {
private JPanel panelLeft;
private JScrollPane scrollPane;
private JPanel movingPanel;
private JPanel upper;
private JPanel lower;
private boolean inited;
private JLabel labelUpper;
private JLabel labelLower;
private JButton scrollBtn;
private Frame.PanelSlideController panelSlideController;
private JButton resizeBtn;
private boolean lowerShowing = false;
public Frame() {
getContentPane().setLayout(new BorderLayout());
panelLeft = new JPanel();
panelLeft.setBackground(Color.CYAN);
panelLeft.setPreferredSize(new Dimension(300, 400));
getContentPane().add(panelLeft, BorderLayout.WEST);
labelUpper = new JLabel("upper");
panelLeft.add(labelUpper);
labelLower = new JLabel("lower");
panelLeft.add(labelLower);
resizeBtn = new JButton("resize");
resizeBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doResize();
}
});
panelLeft.add(resizeBtn);
scrollBtn = new JButton("Scroll");
scrollBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doScroll();
}
});
panelLeft.add(scrollBtn);
scrollPane = new JScrollPane();
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
movingPanel = new JPanel(new GridLayout(2, 1));
movingPanel.setOpaque(false);
movingPanel.setPreferredSize(new Dimension(300, 400));
upper = new JPanel();
upper.setBackground(Color.YELLOW);
movingPanel.add(upper);
lower = new JPanel();
lower.setBackground(Color.RED);
movingPanel.add(lower);
scrollPane.setViewportView(movingPanel);
getContentPane().add(scrollPane, BorderLayout.EAST);
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
inited = true;
}
/**
* This is a manual step instead of overriding validate()
*/
protected void doResize() {
// Get the height we want
int referenceHeight = panelLeft.getSize().height;
// Update the height of the lower panel to equal this
Dimension size = lower.getSize();
size.height = referenceHeight;
lower.setPreferredSize(size);
lower.setMinimumSize(size);
// Update the height of the surrounding panel
size = scrollPane.getSize();
size.height = referenceHeight * 2;
movingPanel.setPreferredSize(size);
movingPanel.setMinimumSize(size);
if (panelSlideController != null) {
panelSlideController.redraw();
System.out.println("redrawing panel slide controller");
}
upper.invalidate();
lower.invalidate();
scrollPane.revalidate();
}
protected void doScroll() {
panelSlideController = new PanelSlideController(scrollPane, 20);
int scrollDirection = lowerShowing ? -1 : 1;
panelSlideController.scrollY(panelLeft.getHeight() * scrollDirection);
lowerShowing = !lowerShowing;
}
@Override
public void validate() {
super.validate();
System.out.println("Validating");
if (inited) {
labelUpper.setText("upper: " + upper.getSize().height);
labelLower.setText("lower: " + lower.getSize().height);
}
}
class PanelSlideController implements ActionListener {
private final JScrollPane scrollPane;
private final int speed;
private Timer timer;
private int endPos;
private boolean scrollingPositive;
public PanelSlideController(JScrollPane scrollPane, int speed) {
this.scrollPane = scrollPane;
this.speed = speed;
}
public void scrollY(int scrollDistance) {
endPos = scrollPane.getViewport().getViewPosition().y + scrollDistance;
scrollingPositive = scrollDistance > 0;
timer = new Timer(speed, this);
timer.start();
}
public void redraw() {
JViewport viewport = scrollPane.getViewport();
Point position = viewport.getViewPosition();
if (scrollingPositive) {
position.y = endPos;
}
else {
position.y = 0;
}
viewport.setViewPosition(position);
}
@Override
public void actionPerformed(ActionEvent e) {
JViewport viewport = scrollPane.getViewport();
Point position = viewport.getViewPosition();
int offset = scrollingPositive ? 10 : -10;
position.y += offset;
viewport.setViewPosition(position);
if ((scrollingPositive && position.y >= endPos)
|| (!scrollingPositive && (position.y <= endPos || position.y <= 0))) {
timer.stop();
}
}
}
public static void main(String[] args) {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Exception e) {
e.printStackTrace();
}
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new Frame().setVisible(true);
}
});
}
}
Right, so the above is runnable. panelLeft (cyan) is used as the reference height. I moved the code out of validate() and instead it's run by clicking resize button.
So you can se the red panel (lower) should not be visible until scroll is clicked, at which point yellow scrolls up while red scroll into view. And then back again in the opposite direction. To do this, I need the yellow panel taking up all the vertical height and if I can do this with a layout manager then yay.
I've updated the original snippet and the 'diagram' to mirror the names used in here.
Thanks