在 JScrollBar 中自动换行的布局
Layout with auto-wrap within JScrollBar
我正在尝试构建一个组件水平放置的面板,如果没有足够的空间则自动换行,还有一个垂直滚动条。
像这样:
+-----------------+
|[1][2][3][4][5] |
| |
+-----------------+
减小宽度:
+-----------+
|[1][2][3] |
|[4][5] |
+-----------+
再次缩小宽度,出现滚动条:
+---------+
|[1][2] ^|
|[3][4] v|
+---------+
我离解决方案不远了:
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);
}
}
}
,但仍然有奇怪的行为:
有了解决方案panel.setPreferredSize(new Dimension(max.width,Integer.MAX_VALUE));
- 调整 window 的大小后,面板变空了。我必须手动移动滚动条才能显示内容
- 滚动条始终可见,但不应该
有了解决方案panel.setPreferredSize(max);
- 调整 window 大小后,面板不会重新布局。我必须再次手动移动 window 以重新布局内容。
- 滚动条永远不可见,而它应该。
该代码有什么建议吗?
[编辑]
我已将原始代码复杂化,并应用了迄今为止提供的建议。
出于设计目的,我想在面板顶部使用 MigLayout。一开始,一切都布置得很好。当放大 window 时,它也有效。但不是减少window。 addComponentListener
没有带来任何附加值。
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);
}
}
下面的代码适合我。
我已将代码拆分为一个组件,该组件执行所有布局和测试代码以验证它是否有效(在 static main
中)方法。
我没有挂钩以调整 JScrollPane 上的事件大小,而是在整个 TestFlow (TF) 组件上这样做 - 我相信这是等效的,但它似乎更安全。每当调整 TF 大小时,我都会更新面板的首选大小,就像您所做的一样 - 但有两个步骤您没有采取:
- 内面板的宽度将与外面板指定的宽度相同,但如果需要,还应为垂直滚动条留出足够的 space。
- 内部面板的高度应该是它显示的最低组件的高度。在我的例子中,我采取了假设最低行中的所有组件高度相同的捷径,但这不一定总是正确的。
深入研究 Swing 如何处理布局和绘画,以及调用几个看起来相似的方法中的哪些方法时,总是让我想起黑魔法。
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
public class TestFlow extends JPanel {
JPanel panel;
JScrollPane scroll;
public TestFlow() {
panel = new JPanel(new FlowLayout());
scroll = new JScrollPane(panel,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
setLayout(new BorderLayout());
add(scroll, BorderLayout.CENTER);
addComponentListener(new ComponentAdapter(){
@Override
public void componentResized(ComponentEvent e){
Rectangle lastChildBounds = panel
.getComponent(panel.getComponentCount() - 1).getBounds();
Dimension preferred = new Dimension(
scroll.getWidth(),
lastChildBounds.y + lastChildBounds.height);
if (scroll.getVerticalScrollBar().isVisible()) {
preferred.width -= scroll.getVerticalScrollBar().getWidth();
}
// System.err.println("setting inner panel size to " + preferred);
panel.setPreferredSize(preferred);
panel.repaint();
}
});
}
public JPanel getInnerPanel() { return panel; }
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
// get a TestFlow instance and fill it with random labels
TestFlow tf = new TestFlow();
Random r = new Random();
for (String s : "A B C D E F G H I J K L M N O".split(" ")) {
String labelText = String.join("", Collections.nCopies(4+r.nextInt(3)*4, s));
JLabel label = new JLabel(labelText);
label.setOpaque(true);
label.setBackground(Color.YELLOW);
tf.getInnerPanel().add(label);
}
// display it in a window
JFrame jf = new JFrame("test");
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.add(tf, BorderLayout.CENTER);
jf.setSize(500, 200);
jf.setLocationRelativeTo(null);
jf.setVisible(true);
});
}
}
可能我误解了你的问题,但是当我添加 revalidate
调用时,面板缩小时一切正常。
Dimension max = new Dimension(scroll.getWidth(), Short.MAX_VALUE);
panel.setMaximumSize(max);
// trigger layout recalculation
panel.revalidate();
panel.repaint();
这里有一些复杂的改进(以便在需要时更好地考虑滚动条和布局的大小 ;)
public class TestMigFlow extends JFrame {
private Integer layoutDecr;
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) {
if (layoutDecr == null && panel.getWidth() > 0) {
layoutDecr = panel.getWidth() - sub.getWidth();
}
JScrollBar vBar = scroll.getVerticalScrollBar();
int decr = vBar.isVisible() ? vBar.getPreferredSize().width : 0;
decr += layoutDecr == null ? 0 : layoutDecr;
Dimension max = new Dimension(scroll.getWidth() - decr, Short.MAX_VALUE);
sub.setMaximumSize(max);
sub.revalidate();
sub.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);
}
}
}
解决方案是
- 等待面板容器的大小调整完毕。因此
addComponentListener
在面板级别而不是帧级别
- 使用 WrapLayout
- 依靠 WrapLayout 的
preferredLayoutSize
计算面板的适当大小
- 在 MigLayout
中使用 "fillx" 选项
调整 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);
}
这同样适用于 BoxLayout 而不是 MigLayout 添加了 2 个:
- 添加 VerticulGlue 作为最后一个元素
为面板指定最大高度以防止 BoxLayout 在面板和垂直胶水之间共享额外的 space。
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);
}
我正在尝试构建一个组件水平放置的面板,如果没有足够的空间则自动换行,还有一个垂直滚动条。
像这样:
+-----------------+
|[1][2][3][4][5] |
| |
+-----------------+
减小宽度:
+-----------+
|[1][2][3] |
|[4][5] |
+-----------+
再次缩小宽度,出现滚动条:
+---------+
|[1][2] ^|
|[3][4] v|
+---------+
我离解决方案不远了:
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);
}
}
}
,但仍然有奇怪的行为:
有了解决方案panel.setPreferredSize(new Dimension(max.width,Integer.MAX_VALUE));
- 调整 window 的大小后,面板变空了。我必须手动移动滚动条才能显示内容
- 滚动条始终可见,但不应该
有了解决方案panel.setPreferredSize(max);
- 调整 window 大小后,面板不会重新布局。我必须再次手动移动 window 以重新布局内容。
- 滚动条永远不可见,而它应该。
该代码有什么建议吗?
[编辑] 我已将原始代码复杂化,并应用了迄今为止提供的建议。
出于设计目的,我想在面板顶部使用 MigLayout。一开始,一切都布置得很好。当放大 window 时,它也有效。但不是减少window。 addComponentListener
没有带来任何附加值。
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);
}
}
下面的代码适合我。
我已将代码拆分为一个组件,该组件执行所有布局和测试代码以验证它是否有效(在 static main
中)方法。
我没有挂钩以调整 JScrollPane 上的事件大小,而是在整个 TestFlow (TF) 组件上这样做 - 我相信这是等效的,但它似乎更安全。每当调整 TF 大小时,我都会更新面板的首选大小,就像您所做的一样 - 但有两个步骤您没有采取:
- 内面板的宽度将与外面板指定的宽度相同,但如果需要,还应为垂直滚动条留出足够的 space。
- 内部面板的高度应该是它显示的最低组件的高度。在我的例子中,我采取了假设最低行中的所有组件高度相同的捷径,但这不一定总是正确的。
深入研究 Swing 如何处理布局和绘画,以及调用几个看起来相似的方法中的哪些方法时,总是让我想起黑魔法。
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
public class TestFlow extends JPanel {
JPanel panel;
JScrollPane scroll;
public TestFlow() {
panel = new JPanel(new FlowLayout());
scroll = new JScrollPane(panel,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
setLayout(new BorderLayout());
add(scroll, BorderLayout.CENTER);
addComponentListener(new ComponentAdapter(){
@Override
public void componentResized(ComponentEvent e){
Rectangle lastChildBounds = panel
.getComponent(panel.getComponentCount() - 1).getBounds();
Dimension preferred = new Dimension(
scroll.getWidth(),
lastChildBounds.y + lastChildBounds.height);
if (scroll.getVerticalScrollBar().isVisible()) {
preferred.width -= scroll.getVerticalScrollBar().getWidth();
}
// System.err.println("setting inner panel size to " + preferred);
panel.setPreferredSize(preferred);
panel.repaint();
}
});
}
public JPanel getInnerPanel() { return panel; }
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
// get a TestFlow instance and fill it with random labels
TestFlow tf = new TestFlow();
Random r = new Random();
for (String s : "A B C D E F G H I J K L M N O".split(" ")) {
String labelText = String.join("", Collections.nCopies(4+r.nextInt(3)*4, s));
JLabel label = new JLabel(labelText);
label.setOpaque(true);
label.setBackground(Color.YELLOW);
tf.getInnerPanel().add(label);
}
// display it in a window
JFrame jf = new JFrame("test");
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.add(tf, BorderLayout.CENTER);
jf.setSize(500, 200);
jf.setLocationRelativeTo(null);
jf.setVisible(true);
});
}
}
可能我误解了你的问题,但是当我添加 revalidate
调用时,面板缩小时一切正常。
Dimension max = new Dimension(scroll.getWidth(), Short.MAX_VALUE);
panel.setMaximumSize(max);
// trigger layout recalculation
panel.revalidate();
panel.repaint();
这里有一些复杂的改进(以便在需要时更好地考虑滚动条和布局的大小 ;)
public class TestMigFlow extends JFrame {
private Integer layoutDecr;
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) {
if (layoutDecr == null && panel.getWidth() > 0) {
layoutDecr = panel.getWidth() - sub.getWidth();
}
JScrollBar vBar = scroll.getVerticalScrollBar();
int decr = vBar.isVisible() ? vBar.getPreferredSize().width : 0;
decr += layoutDecr == null ? 0 : layoutDecr;
Dimension max = new Dimension(scroll.getWidth() - decr, Short.MAX_VALUE);
sub.setMaximumSize(max);
sub.revalidate();
sub.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);
}
}
}
解决方案是
- 等待面板容器的大小调整完毕。因此
addComponentListener
在面板级别而不是帧级别 - 使用 WrapLayout
- 依靠 WrapLayout 的
preferredLayoutSize
计算面板的适当大小 - 在 MigLayout 中使用 "fillx" 选项
调整 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);
}
这同样适用于 BoxLayout 而不是 MigLayout 添加了 2 个:
- 添加 VerticulGlue 作为最后一个元素
为面板指定最大高度以防止 BoxLayout 在面板和垂直胶水之间共享额外的 space。
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); }