How can I get FlowLayout to align JPanels at the bottom like it does for other components?
Asked Answered
I

3

9

I have a case where I am adding JPanels to a FlowLayout, and they are not aligning themselves to the bottom of the layout. I'm using this layout.setAlignOnBaseline(true) and it properly aligns JLabels to the bottom of the panel. However, once those labels are wrapped in panels themselves it no longer works. Here is an example of what I mean, with two panels top and bottom.

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

public class BadLayout {

    private static final Font font1 = new Font("Arial", Font.BOLD, 14);
    private static final Font font2 = new Font("Arial", Font.BOLD, 30);

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Bad layout");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

            FlowLayout layout = new FlowLayout(FlowLayout.LEADING, 0, 0);
            layout.setAlignOnBaseline(true);

            JPanel topPanel = new JPanel();
            topPanel.setLayout(layout);
            topPanel.setBackground(Color.BLACK);
            for (int i = 0; i < 10; i++) {
                JLabel label = new JLabel("Foo");
                label.setForeground(Color.WHITE);
                label.setBackground(Color.RED);
                label.setOpaque(true);
                label.setFont(i % 2 == 0 ? font1 : font2);

                JPanel subPanel = new JPanel();
                subPanel.setLayout(layout);
                subPanel.setBackground(Color.RED);
                subPanel.add(label);
                subPanel.setAlignmentY(Component.BOTTOM_ALIGNMENT);

                topPanel.add(subPanel);
            }

            JPanel bottomPanel = new JPanel();
            bottomPanel.setLayout(layout);
            bottomPanel.setBackground(Color.DARK_GRAY);
            for (int i = 0; i < 10; i++) {
                JLabel label = new JLabel("Foo");
                label.setForeground(Color.WHITE);
                label.setBackground(Color.RED);
                label.setOpaque(true);
                label.setFont(i % 2 == 0 ? font1 : font2);
                bottomPanel.add(label);
            }

            JPanel parentPanel = new JPanel();
            parentPanel.setLayout(new BorderLayout());
            parentPanel.add(topPanel, BorderLayout.NORTH);
            parentPanel.add(bottomPanel, BorderLayout.SOUTH);

            frame.getContentPane().add(parentPanel);
            frame.pack();
            frame.setVisible(true);
        });
    }
}

If you run this code you'll notice the top panel has the smaller "Foo"s centered in the panel, while the one on the bottom has the desired 'bottom-aligned' behavior I'm hoping for. Any ideas how to get the sub JPanels to behave the same?

Inelastic answered 23/8, 2016 at 21:33 Comment(0)
S
7

The API for the setAlignOnBaseline(...) method states:

Components that do not have a baseline will be centered

A JPanel does not have a reasonable baseline to use since components can be on multiple lines depending on the layout manager being used. So it is centered.

I can't tell from your question if you are actually trying to center all the text on the baseline independent of the Font size or whether you are just trying to get all components to be painted at the botton of the panel.

If you are trying to center text on the baseline then maybe you can override the baseline of the panel with code something like:

JPanel subPanel = new JPanel()
{
    @Override
    public int getBaseline(int width, int height)
    {
        Component c = getComponent(0);
        return c.getBaseline(width, height);
    }
};

Of course this would only work if all the components on the panel have the same baseline.

Or if you are just trying to position all the components on the bottom of the panel then you need to use a different layout manager.

You could use the Relative Layout to align all the components to the bottom.

It can be used as a direct replacement to your existing code:

RelativeLayout rl = new RelativeLayout(RelativeLayout.X_AXIS, 0);
rl.setAlignment( RelativeLayout.TRAILING );
JPanel topPanel = new JPanel(rl);

Or if you don't want to use a non JDK class then the BoxLayout or the GridBagLayout would be the way to go.

If you use a BoxLayout then you will need to play with the setAlignmentY(...) property of each component.

If you use the GridBagLayout, then you will need to play with the constraints of each component.

Skullcap answered 23/8, 2016 at 22:39 Comment(1)
Thanks. I realized my intention was to center on the baseline (and not the whole component), so overriding the panel to return the baseline of its components is likely what I want. Ultimately my application is just grouping a bunch of labels together so it shouldn't be bad to override. Thank you.Inelastic
D
2

Myself, I'd use a layout with more "oomph" than FlowLayout for this, such as GridBagLayout. If you use it, you can set the anchor to be SOUTH, and the weighty to be 0.0 which should prevent the component from stretching its height and will seat it at the bottom. For example:

   JPanel topPanel = new JPanel();
    // topPanel.setLayout(layout);
    topPanel.setLayout(new GridBagLayout());
    topPanel.setBackground(Color.BLACK);
    for (int i = 0; i < 10; i++) {
        JLabel label = new JLabel("Foo");
        label.setForeground(Color.WHITE);
        label.setBackground(Color.RED);
        label.setOpaque(true);
        label.setFont(i % 2 == 0 ? font1 : font2);

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = i;
        gbc.gridy = 0;
        gbc.weightx = 1.0;
        gbc.weighty = 0.0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.anchor = GridBagConstraints.SOUTH;

        JPanel subPanel = new JPanel();
        subPanel.setLayout(new BorderLayout());
        subPanel.setBackground(Color.RED);
        subPanel.add(label);
        subPanel.setPreferredSize(subPanel.getMinimumSize());
        // subPanel.setAlignmentY(Component.BOTTOM_ALIGNMENT);   
        topPanel.add(subPanel, gbc);
    }
Dickdicken answered 23/8, 2016 at 21:47 Comment(0)
T
2

After thinking how this could be done without GridBagLayout, I came up with this, even though it is a bit 'hackish':

        JPanel topPanel = new JPanel();
        ((FlowLayout) topPanel.getLayout()).setAlignOnBaseline(true);
        topPanel.setBackground(Color.BLACK);

        int h = 0;
        for (int i = 0; i < 10; i++) {
            JLabel label = new JLabel("Foo");
            label.setForeground(Color.WHITE);
            label.setBackground(Color.GREEN);
            label.setOpaque(true);
            label.setFont(i % 2 == 0 ? font1 : font2);
            JPanel wrapBorder = new JPanel(new BorderLayout());
            JPanel subPanel = new JPanel();
            subPanel.setBackground(Color.RED);
            subPanel.add(label);
            subPanel.setBackground(Color.GREEN);
            wrapBorder.setOpaque(false);
            wrapBorder.add(BorderLayout.SOUTH, subPanel);
            if(wrapBorder.getPreferredSize().height > h) {
                h = wrapBorder.getPreferredSize().height;
            }
            topPanel.add(wrapBorder);
        }

        for(Component component : topPanel.getComponents()) {
            component.setPreferredSize(new Dimension(component.getPreferredSize().width, h));
        }

The only reason I did this is because personally I don't ever use GridBagLayout, but it has its pros (as can be seen ;) ).

I may just be missing a way to make JPanel's expand vertically and I overcomplicated things alot.

Turmoil answered 23/8, 2016 at 22:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.