Pergunta

I have created a JPanel which contains an arbitrary number of JLabels, laid out with a left-aligned flow layout manager. Lets call this a FlowPanel.

Each FlowPanel has a depth, and I would like to stack an arbitrary number of these FlowPanels on top of each other, each one indented by a fixed amount * depth. Ultimately the goal is to display these stacked panels inside a JScrollPane with a vertical scrollbar (if needed) but not a horizontal scrollbar.

I tried stacking the FlowPanels with GridBagLayout, and while it was easy enough to get them to stack vertically and indent the way I want, I can't figure out how to get the width correct. That is, the FlowPanels just run off the right-hand side of my main panel, paying no attention to the displayed width.

I've tried using setPreferredSize, setSize, messing around with the GridBagConstraints... no luck.

Please help! :)

some sample code to demonstrate the problem. This code has 2 issues: 1. the rows should be constant height even when there are only a few rows. 2. the rows scroll off the right side of the screen when the FlowPanels contain too many elements.

public class FlowExample {

public static void main(String[] args) throws Exception {
    JFrame frame = new JFrame();

    StackPanel stackPanel = new StackPanel();
    frame.getContentPane().setLayout(new BorderLayout());
    frame.getContentPane().add(
            new JScrollPane(stackPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER),
            BorderLayout.CENTER);

    frame.setLocation(200, 200);
    frame.setSize(300, 300);
    frame.setVisible(true);

    List<FlowPanel> content = new ArrayList<FlowPanel>();
    for (int i = 0; i < 20; i++) {
        FlowPanel flowPanel = new FlowPanel((int) (4 * Math.random()));
        for (int j = 0; j < (int) (20 * Math.random()); j++) {
            flowPanel.add(new JLabel("label " + j));
        }
        content.add(flowPanel);
        stackPanel.layoutGUI(content);
        Thread.sleep(1000);
    }

    System.in.read();
    System.exit(0);
}
}

class StackPanel extends JPanel {

public StackPanel() {
    setLayout(new BorderLayout());
}

public void layoutGUI(final List<FlowPanel> content) {
    if (!SwingUtilities.isEventDispatchThread()) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                layoutGUI(content);
            }
        });
        return;
    }

    JPanel mainPanel = new JPanel();
    mainPanel.setLayout(new GridLayout(content.size(), 1));
    for (FlowPanel flowPanel : content) {
        flowPanel.setBorder(BorderFactory.createEmptyBorder(0, flowPanel.getDepth() * 10, 0, 0));
        mainPanel.add(flowPanel);
    }

    removeAll();
    add(mainPanel, BorderLayout.CENTER);
    revalidate();
}
}

class FlowPanel extends JPanel {

private int depth;

public FlowPanel(int depth) {
    setLayout(new FlowLayout(FlowLayout.LEFT));
    this.depth = depth;
}

public int getDepth() {
    return depth;
}
}

Here's some sample code using GridBagLayout for laying out the main panel. This is the best result I have gotten, but as you can see, the FlowPanels still don't wrap...

    JPanel mainPanel = new JPanel();
    mainPanel.setLayout(new GridBagLayout());
    GridBagConstraints constraints = new GridBagConstraints();
    constraints.weightx = 1;
    constraints.gridwidth = GridBagConstraints.REMAINDER;
    constraints.anchor = GridBagConstraints.LINE_START;

    for (FlowPanel flowPanel : content) {
        System.out.println("setting border to " + flowPanel.getDepth());
        flowPanel.setBorder(BorderFactory.createEmptyBorder(0, flowPanel.getDepth() * 10, 0, 0));
        mainPanel.add(flowPanel, constraints);
    }

    constraints.weighty = 1;
    mainPanel.add(new JPanel(), constraints);

    removeAll();
    add(mainPanel, BorderLayout.CENTER);
    revalidate();
Foi útil?

Solução

The GridLayout is trying to fit everything on screen for you, so by passing it the list, it readjusts the height each time. By setting a large constant from the start you're telling it to make enough room. Try modifying this line:

mainPanel.setLayout(new GridLayout(content.size(), 1));

to

mainPanel.setLayout(new GridLayout(500, 1));

To stop the scrollbars appearing from the start, an alternative is to use the BoxLayout instead.

JPanel mainPanel = new JPanel();
Box box = Box.createVerticalBox();
mainPanel.add( box );
for (FlowPanel flowPanel : content) {
    flowPanel.setBorder(BorderFactory.createEmptyBorder(0, flowPanel.getDepth() * 10, 0, 0));
    box.add(flowPanel);
}
removeAll();
add(mainPanel, BorderLayout.CENTER);
revalidate();

The wrapping problem is down to FlowLayout only respecting setPreferredSize. You could with the existing code set one preferred size to fit all at the cost of your dynamic resizing requirement. Alternatively, I've used camickr's WrapLayout in the past and that works well.

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

As is, WrapLayout returns different preferred sizes depending on whether a wrap was needed, lazily avoided with a quick hack to WrapLayout.java:

1). add a minimum width variable

private int minWidth = -1;

2). modify the 'layoutSize' method at the last line:

if(minWidth != -1) {
    dim.width = minWidth;
}
return dim;

Once that's done, the only change to your example code is in the FlowPanel constructor:

public FlowPanel(int depth) {
    WrapLayout layout = new WrapLayout(FlowLayout.LEFT);
    layout.setMinWidth(300);
    setLayout(layout);
    setSize(new Dimension(300, 30));
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top