0

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();
Eric Lindauer
  • 1,813
  • 13
  • 19
  • 1
    Have you tried simply stacking them (using a GridLayout would be sufficient), but assigning a left-only empty border to each panel? – JB Nizet Apr 30 '12 at 23:45
  • Doesn't seem to work... writing sample code now to demonstrate. – Eric Lindauer May 01 '12 at 00:10
  • What behaviour do you want when the FlowPanel is wider than the StackPanel? Should it wrap around and increase it's depth? Or do you want to test as you're adding JLabels that you haven't reached the max. width yet? – Capn Sparrow May 01 '12 at 00:44
  • @John Greenhow: If the FlowPanel exceeds the width of the StackPanel, it should wrap around and increase the height of the StackPanel. – Eric Lindauer May 01 '12 at 00:46

1 Answers1

1

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));
}
Capn Sparrow
  • 2,030
  • 2
  • 15
  • 32
  • fyi, I ended up calling the int "preferredWidth" in WrapLayout, and then using it the top of the "layoutSize" method to set the "targetSize" before the rest of the method took effect. This seems to work quite nicely. – Eric Lindauer May 01 '12 at 02:17
  • Agreed, that's a better approach. – Capn Sparrow May 01 '12 at 02:26