1

I have a JPanel using a FlowLayout layout manager and contains components with different sizes.

EDIT: I want to use the FlowLayout because it allows the components to wrap to a new line when the container is resized and they no longer fit next to each other.

The following image depicts the vertical alignment of the FlowLayout on the different components:

How the FlowLayout is aligning the components

How can I modify the FlowLayout to align the top of components as depicted in the following image:

How I would like the FlowLayout to align the components

Here is a code example of the problem:

JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JPanel flowPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
frame.getContentPane().add(flowPanel);

JButton firstComp = new JButton("First");
firstComp.setPreferredSize(new Dimension(200, 300));
flowPanel.add(firstComp);

JButton secondComp = new JButton("Second");
secondComp.setPreferredSize(new Dimension(160, 180));
flowPanel.add(secondComp);

frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
bouncer
  • 173
  • 10

2 Answers2

3

FlowLayout does not by itself support alignment, so unless you actually need the multiple rows behaviour of it, it is better to use a layout manager that supports alignment (such as BoxLayout). It is possible to somewhat work around the issue though, by using the baseline alignment, that FlowLayout can do:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Align {
    private static final int PREF_HEIGHT = 100;

    Align() {
        JFrame frame = new JFrame("Align test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
        JPanel bg = new JPanel();
        ((FlowLayout) bg.getLayout()).setAlignOnBaseline(true);
        frame.add(bg);
        JPanel left = new JPanel();
        left.setBackground(Color.BLUE);
        left.setPreferredSize(new Dimension(100, PREF_HEIGHT));
        bg.add(left);
    
        JPanel right = new JPanel() {
            @Override
            public int getBaseline(int width, int height) {
                return PREF_HEIGHT;
            }
        };
        right.setBackground(Color.GREEN);
        right.setPreferredSize(new Dimension(100, 50));
        bg.add(right);
    
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Align();
            }
        });
    }
}

Results in:

enter image description here

This is, unfortunately, anything but flawless. There's firstly the semi magical way of getting the baseline, which depends on the height of the other components in the panel. Secondly, FlowLayout will reserve too much space for the component when it's moved to a row by it's own - it takes the same amount of space as if it was as high as the other panel. (This can be seen if you add more panels after right). At that point it might be easier to use nested layout for placing the smaller panel than messing with baselines.

Basically, you're better using some other layout manager unless you really can't avoid FlowLayout.

Community
  • 1
  • 1
kiheru
  • 6,588
  • 25
  • 31
  • The reason I want to use the FlowLayout is because it allows the components to wrap to a new line when the container is resized and the components no longer fit next to each other. If it is possible to achieve this behavior with a different layout manager then I will gladly switch. – bouncer Mar 05 '15 at 13:01
  • 1
    @bouncer I don't know about other layout managers that could do it. The [bug report](http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4295966) about the missing alignment support has a workaround which could be useful. – kiheru Mar 05 '15 at 13:08
  • Not sure if I am missing something but the workaround doesn't change anything. – bouncer Mar 05 '15 at 14:18
  • `I want to use the FlowLayout is because it allows the components to wrap to a new line` - this requirement should be part of the question, not added as a comment. – camickr Mar 05 '15 at 16:14
2

The FlowLayout is the only standard JDK layout manager that supports wrapping components to a new line. (There may be third party layout, like MigLayout that support this).

If you don't like the default functionality then you can customize the layout manager. Here is a simple example that lets the FlowLayout do the default layout and then it resets each component to the top of the line:

import java.awt.*;
import java.util.*;
import javax.swing.*;

public class TopFlowLayout extends FlowLayout
{
    @Override
    public void layoutContainer(Container container)
    {
        super.layoutContainer(container);

        Component c = container.getComponent(0);

        int lineStart = getVgap();
        int lineHeight = lineStart + c.getSize().height;

        for (int i = 0; i < container.getComponentCount(); i++)
        {
            c = container.getComponent(i);

            Point p = c.getLocation();
            Dimension d = c.getSize();

            if (p.y < lineHeight) // still on current line
            {
                p.y = lineStart;
                lineHeight = Math.max(lineHeight, lineStart + d.height);
            }
            else  // start a new line
            {
                lineStart = lineHeight + getVgap();
                p.y = lineStart;
                lineHeight = lineStart + d.height;
            }

            p.y = lineStart;
            c.setLocation(p);
        }
    }

    private static void createAndShowGUI()
    {
        TopFlowLayout layout = new TopFlowLayout();
        layout.setAlignment(FlowLayout.LEFT);
        JPanel flowPanel = new JPanel( layout );

        Random random = new Random();

        for (int i = 0; i < 10; i++)
        {
            flowPanel.add( createButton(i + "", random.nextInt(100), random.nextInt(100)) );
        }

        JFrame frame = new JFrame("SSCCE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add( flowPanel );
        frame.setLocationByPlatform( true );
        frame.setSize(400, 400);
        frame.setVisible( true );
    }

    private static JButton createButton(String text, int width, int height)
    {
        JButton button = new JButton(text);
        Dimension size = new Dimension(width + 50, height + 50);
        button.setPreferredSize(size);

        return button;
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
}

You may also want to consider extending the Wrap Layout which is also based on the FlowLayout but adds additional functionality.

camickr
  • 321,443
  • 19
  • 166
  • 288