Dariush Jafari
Dariush Jafari

Reputation: 5443

swing flowlayout preferredSize does not change automatically

I want to have a FlowLayout for MyPanel to add some arbitrary buttons. I added MyPanel into JFrame north ( JFrame has a BorderLayout). My problem is:
When the buttons inside MyPanel occupy more than one row, they are not displayed.
In other words, preferredSize of MyPanel does not change automatically to show the whole content of it. It only shows one row of buttons.

 public class NewMain {
   public static void main(String[] args) {
     JFrame mainFrame = new JFrame();
     mainFrame.getContentPane().setLayout(new BorderLayout());
     mainFrame.setSize(new Dimension(500,500));

     JPanel p1 = new MyPanel();
     p1.setLayout(new FlowLayout());
     mainFrame.add(p1,BorderLayout.NORTH);
     mainFrame.add(new JButton("center"),BorderLayout.CENTER);
     p1.add(new JButton("helllloo1"));
     p1.add(new JButton("helllloo2"));
     p1.add(new JButton("helllloo3"));
     p1.add(new JButton("helllloo4"));
     p1.add(new JButton("helllloo5"));
     p1.add(new JButton("helllloo6"));
     p1.add(new JButton("helllloo7"));

    mainFrame.setVisible(true);
  }

  public static class MyPanel extends JPanel{

  }
}

enter image description here

And when I change MyPanel such that it overrides getPreferredSize and set constant size, it works properly.

public static class MyPanel extends JPanel{

    @Override
    public Dimension getPreferredSize(){
        return new Dimension(-1,100);
    }
}

enter image description here

But I want it to work automatically.
Any help will be appreciated.

Upvotes: 5

Views: 3474

Answers (2)

Jan Bodnar
Jan Bodnar

Reputation: 11637

The reason why this happens lies in the fact that you are nesting two layout managers. Putting it differently, the encountered behaviour is a consequence of how FlowLayout and BorderLayout managers work together.

FlowLayout keeps its children in their preferred size. The preferred size of the panel itself is determined by the height of the tallest component (preferred height) and the sum of the children's widths (preferred width).

BorderLayout calculates the north area the following way: it stretches its child from left to right (not honouring its preferred width) and sets its height to the preferred height of its child (honouring its preferred height).

So the BorderLayout influences how the panel with buttons behaves. It does not allow to create a second row for buttons. Choosing a different layout manager like BoxLayout, the buttons that do not fit in a row appear in another row(s).

Additional comments:

mainFrame.getContentPane().setLayout(new BorderLayout());

The default layout manager of a JFrame's content pane is BorderLayout, no need to set it explicitly.

    JPanel p1 = new MyPanel();
    p1.setLayout(new FlowLayout());

The default manager of an instantiated panel is FlowLayout. So it is again not necessary to set it explicitly.

Upvotes: 2

Dariush Jafari
Dariush Jafari

Reputation: 5443

A layout manager has two main functions:

  • determine the preferred size of the container
  • layout the components in the container based on the layout rules

The FlowLayout is a strange animal. It does both of these functions. The preferred size of the container assumes all components will be laid out in a single row. The layout code will wrap components to the next row when the maximum width of the container is encountered. However, the problem is that the functions don’t talk to one another. When the components are wrapped to a new row, the preferred size doesn't change so you never see the components on the extra row.

What we want is the preferred size to be dynamically calculated as the size of the container is changed. In other words, as the width of the container changes the height will need to be recalculated as well. The WrapLayout extends the FlowLayout to implement this functionality. This will result in synchronizing the preferred size of the container with the layout of the container.

In the following example, the button panel was added to the north of a BorderLayout and the blue panel added to the center. You use the WrapLayout the same as you would use the FlowLayout:

 buttons.setLayout(new WrapLayout());

enter image description here
As the frame is resized smaller, the button panel will increase in height and the blue panel will decrease:

enter image description here

When the panel is added to a scroll pane, the size of the scroll pane won’t change, but horizontal and vertical scrollbars will appear as required.

The initial preferred size calculation of the layout manager still assumes all components will be displayed on a single row. So if you pack() a frame the preferred width may be excessive. You can limit the width of the container by using:

buttons.setSize(new Dimension(300, 1));

the whole code of WrapLayout:

import java.awt.*;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

/**
*  FlowLayout subclass that fully supports wrapping of components.
*/
public class WrapLayout extends FlowLayout
{
private Dimension preferredLayoutSize;

/**
* Constructs a new <code>WrapLayout</code> with a left
* alignment and a default 5-unit horizontal and vertical gap.
*/
public WrapLayout()
{
    super();
}

/**
* Constructs a new <code>FlowLayout</code> with the specified
* alignment and a default 5-unit horizontal and vertical gap.
* The value of the alignment argument must be one of
* <code>WrapLayout</code>, <code>WrapLayout</code>,
* or <code>WrapLayout</code>.
* @param align the alignment value
*/
public WrapLayout(int align)
{
    super(align);
}

/**
* Creates a new flow layout manager with the indicated alignment
* and the indicated horizontal and vertical gaps.
* <p>
* The value of the alignment argument must be one of
* <code>WrapLayout</code>, <code>WrapLayout</code>,
* or <code>WrapLayout</code>.
* @param align the alignment value
* @param hgap the horizontal gap between components
* @param vgap the vertical gap between components
*/
public WrapLayout(int align, int hgap, int vgap)
{
    super(align, hgap, vgap);
}

/**
* Returns the preferred dimensions for this layout given the
* <i>visible</i> 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 <i>visible</i>
* 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 < 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 (rowWidth + d.width > 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 && target.isValid())
    {
        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;
}
}

reference: http://tips4java.wordpress.com/2008/11/06/wrap-layout/

Upvotes: 2

Related Questions