I'm trying to build a panel with components laid out horizontally, with auto-wrap if not enough place and a vertical scrollbar.
Something like this:
+-----------------+
|[1][2][3][4][5] |
| |
+-----------------+
reducing the width:
+-----------+
|[1][2][3] |
|[4][5] |
+-----------+
reducing the width again, the scrollbar appears:
+---------+
|[1][2] ^|
|[3][4] v|
+---------+
I'm not far from a solution:
public class TestFlow extends JFrame {
public TestFlow() {
getContentPane().setLayout(new BorderLayout());
JPanel panel = new JPanel(new FlowLayout());
JScrollPane scroll = new JScrollPane(panel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
getContentPane().add(scroll, BorderLayout.CENTER);
panel.add(new MyComponent("A"));
panel.add(new MyComponent("B"));
panel.add(new MyComponent("C"));
panel.add(new MyComponent("D"));
panel.add(new MyComponent("E"));
panel.add(new MyComponent("F"));
panel.add(new MyComponent("G"));
panel.add(new MyComponent("H"));
panel.add(new MyComponent("I"));
panel.add(new MyComponent("J"));
panel.add(new MyComponent("K"));
panel.add(new MyComponent("L"));
panel.add(new MyComponent("M"));
panel.add(new MyComponent("N"));
panel.add(new MyComponent("O"));
scroll.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
Dimension max=((JScrollPane)e.getComponent()).getViewport().getExtentSize();
// panel.setMaximumSize(new Dimension(max.width,Integer.MAX_VALUE));
panel.setPreferredSize(new Dimension(max.width,Integer.MAX_VALUE));
// panel.setPreferredSize(max);
panel.revalidate();
// panel.repaint();
// System.out.println(panel.getSize().width+"--"+max.width);
}
});
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(500, 200);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new TestFlow().setVisible(true));
}
private static class MyComponent extends JLabel {
public MyComponent(String text) {
super(String.join("", Collections.nCopies((int)(Math.round(Math.random()*4)+4), text)));
setOpaque(true);
setBackground(Color.YELLOW);
}
}
}
, but have still strange behaviours:
With the solution panel.setPreferredSize(new Dimension(max.width,Integer.MAX_VALUE));
With the solution panel.setPreferredSize(max);
Any suggestion in that code ?
[EDIT] I've complicated the original code, and applied the suggestions that were provided until now.
For design purpose, I would like to use a MigLayout on top of my Panel. At start, everything is well laid out. When enlarging the window, it works too. But not on reducing the window. The addComponentListener
does not bring any addedvalue.
public class TestMigFlow extends JFrame {
public TestMigFlow() {
getContentPane().setLayout(new BorderLayout());
JPanel panel = new JPanel(new MigLayout("debug, fill, flowy", "[fill]"));
JScrollPane scroll = new JScrollPane(panel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
getContentPane().add(scroll, BorderLayout.CENTER);
panel.add(new JLabel("A title as spearator "), "growy 0");
JPanel sub = new JPanel(new WrapLayout());
// panel.add(sub, "growx 0"); // Works well on shrink but not on grow
panel.add(sub); // Works well on grow but not on shrink
sub.add(new MyComponent("A"));
sub.add(new MyComponent("B"));
sub.add(new MyComponent("C"));
sub.add(new MyComponent("D"));
sub.add(new MyComponent("E"));
sub.add(new MyComponent("F"));
sub.add(new MyComponent("G"));
sub.add(new MyComponent("H"));
sub.add(new MyComponent("I"));
sub.add(new MyComponent("J"));
sub.add(new MyComponent("K"));
sub.add(new MyComponent("L"));
sub.add(new MyComponent("M"));
sub.add(new MyComponent("N"));
sub.add(new MyComponent("O"));
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
Dimension max = new Dimension(scroll.getWidth(), Short.MAX_VALUE);
panel.setMaximumSize(max);
panel.repaint();
}
});
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(200, 500);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new TestMigFlow().setVisible(true));
}
private static class MyComponent extends JLabel {
public MyComponent(String text) {
super(String.join("", Collections.nCopies((int) (Math.round(Math.random() * 4) + 4), text)));
setOpaque(true);
setBackground(Color.YELLOW);
}
}
The solution is
addComponentListener
at the panel level and not the frame levelpreferredLayoutSize
the compute the adequate size of the panelThe negative-side of the solution is a bit of flickering when resizing the window.
public class TestMigFlow2 extends JFrame {
public TestMigFlow2() {
getContentPane().setLayout(new BorderLayout());
JPanel panel = new JPanel(new MigLayout("fillx, flowy", "[fill]"));
JScrollPane scroll
= new JScrollPane(panel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
getContentPane().add(scroll, BorderLayout.CENTER);
panel.add(new MySeparator("sep1"), "growy 0, shrinky 100");
JPanel sub = new JPanel(new WrapLayout());
panel.add(sub, "shrinky 100"); // Works well on grow but not on shrink
sub.add(new MyComponent("A"));
sub.add(new MyComponent("B"));
sub.add(new MyComponent("C"));
sub.add(new MyComponent("D"));
sub.add(new MyComponent("E"));
sub.add(new MyComponent("F"));
sub.add(new MyComponent("G"));
sub.add(new MyComponent("H"));
sub.add(new MyComponent("I"));
sub.add(new MyComponent("J"));
sub.add(new MyComponent("K"));
sub.add(new MyComponent("L"));
sub.add(new MyComponent("M"));
sub.add(new MyComponent("N"));
sub.add(new MyComponent("O"));
panel.add(new MySeparator("sep2"), "growy 0, shrinky 100");
panel.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
WrapLayout wl = (WrapLayout) sub.getLayout();
Dimension prefdim = wl.preferredLayoutSize(sub);
sub.setPreferredSize(prefdim);
panel.revalidate();
panel.repaint();
}
});
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(200, 500);
setLocationRelativeTo(null);
}
The same also works with a BoxLayout instead of a MigLayout with 2 additions:
giving the panel a max height along to pref height to prevent the BoxLayout to share the extra space between the panel and the Vertical glue.
public TestBoxLayout() {
getContentPane().setLayout(new BorderLayout());
JPanel panel = new JPanel();
BoxLayout layout = new BoxLayout(panel, BoxLayout.PAGE_AXIS);
panel.setLayout(layout);
JScrollPane scroll
= new JScrollPane(panel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
getContentPane().add(scroll, BorderLayout.CENTER);
JLabel separator;
separator = new MySeparator("Sep1");
panel.add(separator);
JPanel sub = new MyPanel(new WrapLayout());
sub.setAlignmentX(0f);
panel.add(sub);
sub.add(new MyComponent("A"));
sub.add(new MyComponent("B"));
sub.add(new MyComponent("C"));
sub.add(new MyComponent("D"));
sub.add(new MyComponent("E"));
sub.add(new MyComponent("F"));
sub.add(new MyComponent("G"));
sub.add(new MyComponent("H"));
sub.add(new MyComponent("I"));
sub.add(new MyComponent("J"));
sub.add(new MyComponent("K"));
sub.add(new MyComponent("L"));
sub.add(new MyComponent("M"));
sub.add(new MyComponent("N"));
sub.add(new MyComponent("O"));
separator = new MySeparator("Sep2");
panel.add(separator);
// -- Un filler --
panel.add(Box.createVerticalGlue());
panel.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
WrapLayout wl=(WrapLayout) sub.getLayout();
Dimension prefdim=wl.preferredLayoutSize(sub);
sub.setPreferredSize(prefdim);
// Force the max height = pref height to prevent the BoxLayout dispatching the remaining height between the panel and the glue.
Dimension maxdim=new Dimension(Short.MAX_VALUE,prefdim.height);
sub.setMaximumSize(maxdim);
panel.revalidate();
panel.repaint();
}
});
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(200, 500);
setLocationRelativeTo(null);
}