FlowLayout no usa varias líneas dentro de GridBagLayout

Normally, FlowLayout uses more than one line if needed. Apparently this doesn't happen if the component with the FlowLayout is itself part of a GridBagLayout.

Considere este código:

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

public class Xyzzy extends JFrame{
    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Xyzzy frame = new Xyzzy();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                frame.setLayout(new GridBagLayout());

                JPanel top = new JPanel();
                top.setLayout(new FlowLayout());
                for (int i=1; i<=30; ++i)
                    top.add(new JLabel(String.format("Label #%d",i)));

                GridBagConstraints c = new GridBagConstraints();
                c.weightx = 1.0;
                c.gridwidth = GridBagConstraints.REMAINDER;
                c.anchor = GridBagConstraints.CENTER;

                frame.add(top,c);
                frame.add(new JLabel("Bottom"),c);

                //top.setPreferredSize(new Dimension(500,300));

                frame.setSize(600, 600);
                frame.setVisible(true);
            }
        });
    }
}

This code is intended to display the JLabels "Label #1", "Label #2" etc. on multiple lines, but in fact it only uses one line.

I can force it to use multiple lines by removing the '//' before the call to setPreferredSize in the above code, but this requires me to set both a width and a height, and I don't know what height to use. (I cannot use FontMetrics to calculate the height, because in my actual case the JLabels are in reality small JPanels of varying size.)

So is there a way to force FlowLayout to use multiple lines? (Or, alternatively, is there a way to calculate the required height of a component when its width is known?)

preguntado el 08 de noviembre de 11 a las 11:11

Have you considered using other layout managers? -

2 Respuestas

Try to add constraint parameters:

c.weighty = 1.0;
c.fill = GridBagConstraints.VERTICAL;

It should allow growing of panel with FlowLayout in vertical direction.

respondido 12 nov., 11:19

it must work, check what you set constraint parameters before adding components. - Eugene

Oops, I made a mistake when copying your code into mine. My sincere apologies! Yes, your correction does work, thank you. - oz1cz

Según entiendo, FlowLayout does require a preferred size in order to actually wrap anything. I've created a subclass which works out the preferred size as the parent container's width, and then whatever height is required:

public class WrappingFlowLayout extends FlowLayout {
@Override
public Dimension preferredLayoutSize(Container target) {
    synchronized (target.getTreeLock()) {
        Dimension result;
        int w = target.getWidth();

        if (w == 0) {
            // The container hasn't been assigned any size yet; just behave like a regular flow layout.
            result = super.preferredLayoutSize(target);
        } else {
            Insets insets = target.getInsets();
            int wrapW = w - insets.left - insets.right;
            int maxW = 0; // Width of the widest row.
            int rowH = 0; // Current row height.
            int x = 0;
            int y = 0;
            boolean firstVisibleComponent = true;

            for (Component c : target.getComponents()) {
                if (c.isVisible()) {
                    Dimension d = c.getPreferredSize();
                    if (firstVisibleComponent) {
                        x = d.width + (getHgap() * 2);
                        y = getVgap();
                        rowH = d.height;
                        firstVisibleComponent = false;
                    } else if (x + d.width + getHgap() <= wrapW) {
                        // Add to current row.
                        x += d.width + getHgap();
                        rowH = Math.max(rowH, d.height);
                    } else {
                        // New row.
                        x = d.width + (getHgap() * 2);
                        y += rowH + getVgap();
                        rowH = d.height;
                    }
                    maxW = Math.max(maxW, x);
                }
            }
            y += rowH + getVgap();

            result = new Dimension(maxW + insets.left + insets.right, y + insets.top + insets.bottom);
        }
        return result;
    }
}
}

Note that you'll need to listen for resize events on the parent component (top in your code) and trigger an update of the preferred size. Your best bet for doing this is probably a ComponentListener.

respondido 08 nov., 11:17

No, that does not work. There are two problems with your code if I use it in my program: Firstly, I still see only one line when the window opens. But once I resize the window, all the lines show. Secondly (and much more important), when resizing the window, the lines wrap nicely if I reduce the size of the window, but nothing happens if I increase the size of the window. - oz1cz

Edited with a note about listening for panel resize events. Pretty sure it should work if you add that part. - vaughandroid

I'm not sure how to trigger an update to the preferred size. I added a ComponentListener a topy en su componentResized método que llamé top.invalidate(). This means that I now see multiple lines when the program starts, but it does not cause a recalculation of the preferred size when the window grows. - oz1cz

That sounds like it should've worked, not sure why it didn't. Moot point now as you've accepted Eugene's answer now anyway! - vaughandroid

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.