Java – 具有水平滚动的垂直“FlowLayout”

正如标题中所描述的那样,我一直试图设置一种带有水平滚动的垂直流布局。 布局中的组件将是JLabel。 让我画一幅画:

+-------------------------+ <--- window |Label1 Label4 Label7| |Label2 Label5 Label8| <--- labels |Label3 Label6 Label9| |========| <--- scrollbar +-------------------------+ 

同一窗口,垂直扩展

 +--------------------------+ <--- window |Label1 Label5 Label9 | |Label2 Label6 Label10| <--- labels |Label3 Label7 Label11| |Label4 Label8 Label12| |=====| <--- scrollbar +--------------------------+ 

因此,标签将填充可用的垂直空间,然后创建一个新列。 一旦可用的水平空间耗尽,就会出现水平滚动条。

通常不应出现垂直滚动条; 但是,如果窗口的垂直高度非常小,那么有一个垂直滚动条会很不错。

任何帮助是极大的赞赏。 我是Java的新手,所以任何额外的解释都会很精彩。 谢谢!

编辑:

根据以下回复,我现在正在使用: http : //tips4java.wordpress.com/2008/11/06/wrap-layout/和http://code.google.com/p/verticalflowlayout/

我有WrapLayout扩展VerticalFlowLayout:

 package LogicSim; import java.awt.*; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; /** * FlowLayout subclass that fully supports wrapping of components. */ public class VerticalWrapLayout extends VerticalFlowLayout { private Dimension preferredLayoutSize; /** * Constructs a new WrapLayout with a left * alignment and a default 5-unit horizontal and vertical gap. */ public VerticalWrapLayout() { super(); } /** * Constructs a new FlowLayout with the specified * alignment and a default 5-unit horizontal and vertical gap. * The value of the alignment argument must be one of * WrapLayout, WrapLayout, * or WrapLayout. * @param align the alignment value */ public VerticalWrapLayout(int align) { super(align); } /** * Creates a new flow layout manager with the indicated alignment * and the indicated horizontal and vertical gaps. * 

* The value of the alignment argument must be one of * WrapLayout, WrapLayout, * or WrapLayout. * @param align the alignment value * @param hgap the horizontal gap between components * @param vgap the vertical gap between components */ public VerticalWrapLayout(int align, int hgap, int vgap) { super(align, hgap, vgap); } /** * Returns the preferred dimensions for this layout given the * visible components in the specified target container. * @param target the component which needs to be laid out * @return the preferred dimensions to lay out the * subcomponents of the specified container */ @Override public Dimension preferredLayoutSize(Container target) { return layoutSize(target, true); } /** * Returns the minimum dimensions needed to layout the visible * components contained in the specified target container. * @param target the component which needs to be laid out * @return the minimum dimensions to lay out the * subcomponents of the specified container */ @Override public Dimension minimumLayoutSize(Container target) { Dimension minimum = layoutSize(target, false); minimum.width -= (getHgap() + 1); return minimum; } /** * Returns the minimum or preferred dimension needed to layout the target * container. * * @param target target to get layout size for * @param preferred should preferred size be calculated * @return the dimension to layout the target container */ private Dimension layoutSize(Container target, boolean preferred) { synchronized (target.getTreeLock()) { // Each row must fit with the width allocated to the containter. // When the container width = 0, the preferred width of the container // has not yet been calculated so lets ask for the maximum. int targetWidth = target.getSize().width; if (targetWidth == 0) targetWidth = Integer.MAX_VALUE; int hgap = getHgap(); int vgap = getVgap(); Insets insets = target.getInsets(); int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2); int maxWidth = targetWidth - horizontalInsetsAndGap; // Fit components into the allowed width Dimension dim = new Dimension(0, 0); int rowWidth = 0; int rowHeight = 0; int nmembers = target.getComponentCount(); for (int i = 0; i maxWidth) { addRow(dim, rowWidth, rowHeight); rowWidth = 0; rowHeight = 0; } // Add a horizontal gap for all components after the first if (rowWidth != 0) { rowWidth += hgap; } rowWidth += d.width; rowHeight = Math.max(rowHeight, d.height); } } addRow(dim, rowWidth, rowHeight); dim.width += horizontalInsetsAndGap; dim.height += insets.top + insets.bottom + vgap * 2; // When using a scroll pane or the DecoratedLookAndFeel we need to // make sure the preferred size is less than the size of the // target containter so shrinking the container size works // correctly. Removing the horizontal gap is an easy way to do this. Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target); if (scrollPane != null) { dim.width -= (hgap + 1); } return dim; } } /* * A new row has been completed. Use the dimensions of this row * to update the preferred size for the container. * * @param dim update the width and height when appropriate * @param rowWidth the width of the row to add * @param rowHeight the height of the row to add */ private void addRow(Dimension dim, int rowWidth, int rowHeight) { dim.width = Math.max(dim.width, rowWidth); if (dim.height > 0) { dim.height += getVgap(); } dim.height += rowHeight; } }

这是我的框架设置:

  JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(300, 300); frame.setVisible(true); JPanel panel = new JPanel(); panel.setLayout( new VerticalWrapLayout(0) ); JScrollPane pane = new JScrollPane(panel); frame.add( pane, BorderLayout.CENTER ); for (int i=0; i < 80; i++ ) { panel.add( new JLabel( "Label" + i ) ); } 

现在,这将按照我之后的方式在垂直列中设置标签,但它仍然会创建垂直滚动条。 在修改VerticalWrapLayout类时,我非常不稳定。 另外,我真的不明白JScrollPane如何与这些类进行交互。 有关如何进行的任何建议?


解决了! 请参阅下面的答案以及我的答案。

以下是VerticalFlowLayout的示例。 它是FlowLayout类的副本,其中一些逻辑被更改为“垂直”导向而不是“水平”导向:

 import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * A flow layout arranges components in a directional flow, much * like lines of text in a paragraph. The flow direction is * determined by the container's componentOrientation * property and may be one of two values: * 
    *
  • ComponentOrientation.TOP_TO_BOTTOM *
  • ComponentOrientation.BOTTOM_TO_TOP *
* Flow layouts are typically used * to arrange buttons in a panel. It arranges buttons * horizontally until no more buttons fit on the same line. * The line alignment is determined by the align * property. The possible values are: *
    *
  • {@link #TOP TOP} *
  • {@link #BOTTOM BOTTOM} *
  • {@link #CENTER CENTER} *
  • {@link #LEADING LEADING} *
  • {@link #TRAILING TRAILING} *
*

*/ public class VerticalFlowLayout implements LayoutManager, java.io.Serializable { /** * This value indicates that each row of components * should be left-justified. */ public static final int TOP = 0; /** * This value indicates that each row of components * should be centered. */ public static final int CENTER = 1; /** * This value indicates that each row of components * should be right-justified. */ public static final int BOTTOM = 2; /** * align is the property that determines * how each column distributes empty space. * It can be one of the following three values: *

    * TOP * BOTTOM * CENTER *
* * @see #getAlignment * @see #setAlignment */ int align; // This is the one we actually use /** * The flow layout manager allows a seperation of * components with gaps. The horizontal gap will * specify the space between components and between * the components and the borders of the * Container. * * @see #getHgap() * @see #setHgap(int) */ int hgap; /** * The flow layout manager allows a seperation of * components with gaps. The vertical gap will * specify the space between rows and between the * the rows and the borders of the Container. * * @see #getHgap() * @see #setHgap(int) */ int vgap; /** * Constructs a new VerticalFlowLayout with a centered alignment and a * default 5-unit horizontal and vertical gap. */ public VerticalFlowLayout() { this(CENTER, 5, 5); } /** * Constructs a new VerticalFlowLayout with the specified * alignment and a default 5-unit horizontal and vertical gap. * The value of the alignment argument must be one of * VerticalFlowLayout.TOP, VerticalFlowLayout.BOTTOM, * or VerticalFlowLayout.CENTER * @param align the alignment value */ public VerticalFlowLayout(int align) { this(align, 5, 5); } /** * Creates a new flow layout manager with the indicated alignment * and the indicated horizontal and vertical gaps. *

* The value of the alignment argument must be one of * VerticalFlowLayout.TOP, VerticalFlowLayout.BOTTOM, * or VerticalFlowLayout.CENTER. * @param align the alignment value * @param hgap the horizontal gap between components * and between the components and the * borders of the Container * @param vgap the vertical gap between components * and between the components and the * borders of the Container */ public VerticalFlowLayout(int align, int hgap, int vgap) { this.hgap = hgap; this.vgap = vgap; setAlignment(align); } /** * Gets the alignment for this layout. * Possible values are VerticalFlowLayout.TOP, * VerticalFlowLayout.BOTTOM or VerticalFlowLayout.CENTER, * @return the alignment value for this layout * @see java.awt.VerticalFlowLayout#setAlignment * @since JDK1.1 */ public int getAlignment() { return align; } /** * Sets the alignment for this layout. Possible values are *

    *
  • VerticalFlowLayout.TOP *
  • VerticalFlowLayout.BOTTOM *
  • VerticalFlowLayout.CENTER *
* @param align one of the alignment values shown above * @see #getAlignment() * @since JDK1.1 */ public void setAlignment(int align) { this.align = align; } /** * Gets the horizontal gap between components * and between the components and the borders * of the Container * * @return the horizontal gap between components * and between the components and the borders * of the Container * @see java.awt.VerticalFlowLayout#setHgap * @since JDK1.1 */ public int getHgap() { return hgap; } /** * Sets the horizontal gap between components and * between the components and the borders of the * Container. * * @param hgap the horizontal gap between components * and between the components and the borders * of the Container * @see java.awt.VerticalFlowLayout#getHgap * @since JDK1.1 */ public void setHgap(int hgap) { this.hgap = hgap; } /** * Gets the vertical gap between components and * between the components and the borders of the * Container. * * @return the vertical gap between components * and between the components and the borders * of the Container * @see java.awt.VerticalFlowLayout#setVgap * @since JDK1.1 */ public int getVgap() { return vgap; } /** * Sets the vertical gap between components and between * the components and the borders of the Container. * * @param vgap the vertical gap between components * and between the components and the borders * of the Container * @see java.awt.VerticalFlowLayout#getVgap */ public void setVgap(int vgap) { this.vgap = vgap; } /** * Adds the specified component to the layout. * Not used by this class. * @param name the name of the component * @param comp the component to be added */ public void addLayoutComponent(String name, Component comp) { } /** * Removes the specified component from the layout. * Not used by this class. * @param comp the component to remove * @see java.awt.Container#removeAll */ public void removeLayoutComponent(Component comp) { } /** * Returns the preferred dimensions for this layout given the * visible components in the specified target container. * * @param target the container that needs to be laid out * @return the preferred dimensions to lay out the * subcomponents of the specified container * @see Container * @see #minimumLayoutSize * @see java.awt.Container#getPreferredSize */ public Dimension preferredLayoutSize(Container target) { synchronized (target.getTreeLock()) { Dimension dim = new Dimension(0, 0); int nmembers = target.getComponentCount(); boolean firstVisibleComponent = true; for (int i = 0 ; i < nmembers ; i++) { Component m = target.getComponent(i); if (m.isVisible()) { Dimension d = m.getPreferredSize(); dim.width = Math.max(dim.width, d.width); if (firstVisibleComponent) { firstVisibleComponent = false; } else { dim.height += vgap; } dim.height += d.height; } } Insets insets = target.getInsets(); dim.width += insets.left + insets.right + hgap*2; dim.height += insets.top + insets.bottom + vgap*2; return dim; } } /** * Returns the minimum dimensions needed to layout the visible * components contained in the specified target container. * @param target the container that needs to be laid out * @return the minimum dimensions to lay out the * subcomponents of the specified container * @see #preferredLayoutSize * @see java.awt.Container * @see java.awt.Container#doLayout */ public Dimension minimumLayoutSize(Container target) { synchronized (target.getTreeLock()) { Dimension dim = new Dimension(0, 0); int nmembers = target.getComponentCount(); boolean firstVisibleComponent = true; for (int i = 0 ; i < nmembers ; i++) { Component m = target.getComponent(i); if (m.isVisible()) { Dimension d = m.getMinimumSize(); dim.width = Math.max(dim.width, d.width); if (firstVisibleComponent) { firstVisibleComponent = false; } else { dim.height += vgap; } dim.height += d.height; } } Insets insets = target.getInsets(); dim.width += insets.left + insets.right + hgap*2; dim.height += insets.top + insets.bottom + vgap*2; return dim; } } /** * Lays out the container. This method lets each * visible component take * its preferred size by reshaping the components in the * target container in order to satisfy the alignment of * this VerticalFlowLayout object. * * @param target the specified component being laid out * @see Container * @see java.awt.Container#doLayout */ public void layoutContainer(Container target) { synchronized (target.getTreeLock()) { Insets insets = target.getInsets(); int maxHeight = target.getSize().height - (insets.top + insets.bottom + vgap*2); int nmembers = target.getComponentCount(); int x = insets.left + hgap; int y = 0; int columnWidth = 0; int start = 0; boolean ttb = target.getComponentOrientation().isLeftToRight(); for (int i = 0 ; i < nmembers ; i++) { Component m = target.getComponent(i); if (m.isVisible()) { Dimension d = m.getPreferredSize(); m.setSize(d.width, d.height); if ((y == 0) || ((y + d.height) <= maxHeight)) { if (y > 0) { y += vgap; } y += d.height; columnWidth = Math.max(columnWidth, d.width); } else { moveComponents(target, x, insets.top + vgap, columnWidth, maxHeight - y, start, i, ttb); y = d.height; x += hgap + columnWidth; columnWidth = d.width; start = i; } } } moveComponents(target, x, insets.top + vgap, columnWidth, maxHeight - y, start, nmembers, ttb); } } /** * Centers the elements in the specified row, if there is any slack. * @param target the component which needs to be moved * @param x the x coordinate * @param y the y coordinate * @param width the width dimensions * @param height the height dimensions * @param columnStart the beginning of the column * @param columnEnd the the ending of the column */ private void moveComponents( Container target, int x, int y, int width, int height, int columnStart, int columnEnd, boolean ttb) { switch (align) { case TOP: y += ttb ? 0 : height; break; case CENTER: y += height / 2; break; case BOTTOM: y += ttb ? height : 0; break; } for (int i = columnStart ; i < columnEnd ; i++) { Component m = target.getComponent(i); if (m.isVisible()) { int cx; cx = x + (width - m.getSize().width) / 2; if (ttb) { m.setLocation(cx, y); } else { m.setLocation(cx, target.getSize().height - y - m.getSize().height); } y += m.getSize().height + vgap; } } } /** * Returns a string representation of this VerticalFlowLayout * object and its values. * @return a string representation of this layout */ public String toString() { String str = ""; switch (align) { case TOP: str = ",align=top"; break; case CENTER: str = ",align=center"; break; case BOTTOM: str = ",align=bottom"; break; } return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + str + "]"; } public static void main(String[] args) { JPanel main = new JPanel( new BorderLayout() ); final JPanel buttons = new JPanel(new VerticalFlowLayout() ); // buttons.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); main.add(buttons, BorderLayout.CENTER); for (int i = 0; i < 7; i++) { buttons.add( new JRadioButton("button " + i) ); } JButton button = new JButton("Add Radio Button"); main.add(button, BorderLayout.SOUTH); button.addActionListener( new ActionListener() { private int i = 8; public void actionPerformed(ActionEvent e) { buttons.add( new JRadioButton("button R Us" + i++) ); buttons.revalidate(); // pack(); } }); JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(main); frame.setSize(300, 300); frame.setLocationRelativeTo(null); frame.setVisible(true); } }

从理论上讲,你应该能够使用WrapLayout并让它扩展这个类,然后自定义代码也是垂直方向的。

编辑:

我有WrapLayout扩展VerticalFlowLayout:

你不能只扩展VerticalFlowLayout。 WrapLayout代码用于根据父容器的大小计算固定宽度。 您需要更改计算固定高度的行为。 我没有尝试过,但基本上你需要将“width”相关变量引用更改为“height”和“height”相关的变量引用“width”,这样代码就可以在垂直维度而不是水平维度上工作。

这是我使用的修改后的VerticalWrapLayout ,万一有人感兴趣!

 package LogicSim; import java.awt.*; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; /** * FlowLayout subclass that fully supports wrapping of components. */ public class VerticalWrapLayout extends VerticalFlowLayout { private Dimension preferredLayoutSize; /** * Constructs a new WrapLayout with a left * alignment and a default 5-unit horizontal and vertical gap. */ public VerticalWrapLayout() { super(); } /** * Constructs a new FlowLayout with the specified * alignment and a default 5-unit horizontal and vertical gap. * The value of the alignment argument must be one of * WrapLayout, WrapLayout, * or WrapLayout. * @param align the alignment value */ public VerticalWrapLayout(int align) { super(align); } /** * Creates a new flow layout manager with the indicated alignment * and the indicated horizontal and vertical gaps. * 

* The value of the alignment argument must be one of * WrapLayout, WrapLayout, * or WrapLayout. * @param align the alignment value * @param hgap the horizontal gap between components * @param vgap the vertical gap between components */ public VerticalWrapLayout(int align, int hgap, int vgap) { super(align, hgap, vgap); } /** * Returns the preferred dimensions for this layout given the * visible components in the specified target container. * @param target the component which needs to be laid out * @return the preferred dimensions to lay out the * subcomponents of the specified container */ @Override public Dimension preferredLayoutSize(Container target) { return layoutSize(target, true); } /** * Returns the minimum dimensions needed to layout the visible * components contained in the specified target container. * @param target the component which needs to be laid out * @return the minimum dimensions to lay out the * subcomponents of the specified container */ @Override public Dimension minimumLayoutSize(Container target) { Dimension minimum = layoutSize(target, false); minimum.height -= (getVgap() + 1); return minimum; } /** * Returns the minimum or preferred dimension needed to layout the target * container. * * @param target target to get layout size for * @param preferred should preferred size be calculated * @return the dimension to layout the target container */ private Dimension layoutSize(Container target, boolean preferred) { synchronized (target.getTreeLock()) { // Each row must fit with the width allocated to the containter. // When the container width = 0, the preferred width of the container // has not yet been calculated so lets ask for the maximum. int targetHeight = target.getSize().height; if (targetHeight == 0) targetHeight = Integer.MAX_VALUE; int hgap = getHgap(); int vgap = getVgap(); Insets insets = target.getInsets(); int verticalInsetsAndGap = insets.top + insets.bottom + (vgap * 2); int maxHeight = targetHeight - verticalInsetsAndGap; // Fit components into the allowed height Dimension dim = new Dimension(0, 0); int rowWidth = 0; int rowHeight = 0; int nmembers = target.getComponentCount(); for (int i = 0; i < nmembers; i++) { Component m = target.getComponent(i); if (m.isVisible()) { Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize(); // Can't add the component to current row. Start a new row. if (rowHeight + d.height > maxHeight) { addColumn(dim, rowWidth, rowHeight); rowWidth = 0; rowHeight = 0; } // Add a horizontal gap for all components after the first if (rowHeight != 0) { rowHeight += vgap; } // ****************************************************** rowHeight += d.height; rowWidth = Math.max(rowWidth, d.width); } } addColumn(dim, rowWidth, rowHeight); dim.height += verticalInsetsAndGap; dim.width += insets.left + insets.right + hgap * 2; //When using a scroll pane or the DecoratedLookAndFeel we need to // make sure the preferred size is less than the size of the // target containter so shrinking the container size works // correctly. Removing the horizontal gap is an easy way to do this. Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target); if (scrollPane != null) { dim.height -= (vgap + 1); } return dim; } } /* * A new row has been completed. Use the dimensions of this row * to update the preferred size for the container. * * @param dim update the width and height when appropriate * @param rowWidth the width of the row to add * @param rowHeight the height of the row to add */ private void addColumn(Dimension dim, int rowWidth, int rowHeight) { dim.height = Math.max(dim.height, rowHeight); if (dim.width > 0) { dim.width += getHgap(); } dim.width += rowWidth; } }

谢谢大家!

你可以使用BoxLayout :

例如:

在此处输入图像描述

 import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; public class CustomFrame extends JFrame { private int labelCounter = 0; private int maxLabels = 3; private Box box; private JPanel pane; public CustomFrame() { super("Custom JFrame"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); pane = new JPanel(); pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS)); JScrollPane scr = new JScrollPane(pane); add(scr); for(int i = 1; i <= 15; i++) addNewLabel("Label " + i); setSize(200,130); setVisible(true); } private void addNewLabel(String s) { if(labelCounter % maxLabels == 0) { box = Box.createVerticalBox(); box.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); } box.add(new JLabel(s)); pane.add(box); labelCounter++; } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { new CustomFrame(); } }); } } 

我调查了你的问题,据我所见,有多个VerticalFlowLayouts不可以包装,还有wrappableFlowLayouts ,它们不是垂直的。

而不是把它们放在一起(聪明的人会做什么)我写了一个糟糕的解决方法,基于你的答案。 它完成了工作,但遗憾的是它并没有像我想要的那样光滑/可靠。 但我还以为我发布了它。

 public class CustomFrame extends JFrame { private int borderWidth = 10; private int labelCounter = 0; private Box box; private JPanel pane; private List registeredLabels = new ArrayList(); public CustomFrame() { super("Custom JFrame"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); pane = new JPanel(); pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS)); JScrollPane scrollPane = new JScrollPane(pane); scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); add(scrollPane); setSize(200, 130); // this just calls the method reAddAllLabels() upon resizing this.addComponentListener(new ComponentListener() { @Override public void componentShown(ComponentEvent e) { // ignore } @Override public void componentResized(ComponentEvent e) { reAddAllLabels(); repaint(); } @Override public void componentMoved(ComponentEvent e) { // ignore } @Override public void componentHidden(ComponentEvent e) { // ignore } }); setVisible(true); } /** * Build the Label, register the Label in the Label-List, add the Label through further method * * @param text * for the new Label */ public void addNewLabel(String text) { JLabel myJLabel = new JLabel(text); registeredLabels.add(myJLabel); addLabel(myJLabel); } /** * Reset stuff, add all registered Labels * */ private void reAddAllLabels() { labelCounter = 0; pane.removeAll(); if (registeredLabels.size() > 0) { for (JLabel label : registeredLabels) { addLabel(label); } } } /** * Calculate max-Labels per Column, eventually create new Box, add Label to box * * @param label */ private void addLabel(JLabel label) { int maxLabels = (pane.getHeight() - borderWidth * 2) / label.getPreferredSize().height; if (labelCounter % maxLabels == 0) { box = Box.createVerticalBox(); box.setBorder(BorderFactory.createEmptyBorder(borderWidth, borderWidth, borderWidth, borderWidth)); } box.add(label); pane.add(box); labelCounter++; } /** * How to use the frame * * @param args */ public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { CustomFrame customFrame = new CustomFrame(); for (int i = 1; i <= 20; i++) { customFrame.addNewLabel("Label " + i); } } }); } }