Problem with JScrollPane

Hello,

Here's my problem. My Application uses the following component hierachy:

JFrame (BorderLayout)

+-JScrollPane (BorderLayout.CENTER)

+- Box (Y_AXIS)

+- Box (Y_AXIS)

+- WrappingPanel

+- WrappingPanel

+- WrappingPanel

+- Box (Y_AXIS)

+- WrappingPanel

+- WrappingPanel

+- WrappingPanel

+- Box (Y_AXIS)

+- WrappingPanel

+- WrappingPanel

+- WrappingPanel

+- Box (Y_AXIS)

+- WrappingPanel

+- WrappingPanel

+- WrappingPanel

the Wrapping Panel is just a JPanel that contains elements that are supposed to wrap. Some are a JPanel with a FlowLayout that contains many many labels, and some are a JPanel with one single JLabel with a long text (I make it wrap by enclosing my string between <html> and </html> tags).

Basically, I need the JScrollPane to do vertical scrolling only. I want all of my boxes to be as wide as the JScrollPane viewport (not narrower, not wider), so they can take up all the space but not require horizontal scrolling.

My problem is that none of my WrappingPanel actually wraps. Everything is displayed on one single line. This is apparently because the JScrollPane doesn't fix the width of it's children the way I need it to. The WrappingPanels do work (wrap their content on multiple lines) when they are placed out of the JScrollPane.

I've tried a lot of things (including subclassing JViewport and ViewportLayout) but none of them seem to work appropriately.

Can you please help me find a way to do this what I need nicely?

Thanks in advance,

Nicolas Piguet

[1669 byte] By [npigueta] at [2007-11-26 14:19:03]
# 1
You might be able to use my WrapLayout. Search the forum for the code.
camickra at 2007-7-8 2:09:56 > top of Java-index,Desktop,Core GUI APIs...
# 2

Hmmm...

Your WrapLayout seems to work, but has some strange glitches on the border (well, at least the version I have fetched has, but maybe it's not the latest version).

Anyway, of all the answers I've been able to find on this forum, this layout manager seems to be the best...

/* Found at http://forum.java.sun.com/thread.jspa?forumID=57&threadID=780453 */

package learner.ui;

import java.awt.*;

public class BetterFlowLayout extends FlowLayout {

public BetterFlowLayout() {

super();

}

public BetterFlowLayout(int align) {

super(align);

}

public BetterFlowLayout(int align, int hgap, int vgap) {

super(align, hgap, vgap);

}

@Override

public Dimension preferredLayoutSize(Container target) {

return betterPreferredSize(target);

}

@Override

public Dimension minimumLayoutSize(Container target) {

return betterPreferredSize(target);

}

public Dimension betterPreferredSize(Container target) {

synchronized (target.getTreeLock()) {

Insets insets = target.getInsets();

int maxwidth = target.getWidth() - (insets.left + insets.right + getHgap()*2);

int nmembers = target.getComponentCount();

int x = 0, y = insets.top + getVgap();

int rowh = 0;

for (int i = 0 ; i < nmembers ; i++) {

Component m = target.getComponent(i);

if (m.isVisible()) {

Dimension d = m.getPreferredSize();

m.setSize(d.width, d.height);

if ((x == 0) || ((x + d.width) <= maxwidth)) {

// the component fits on the current line

if (x > 0) {

x += getHgap();

}

x += d.width;

rowh = Math.max(rowh, d.height);

} else {

// we need to put the component on a new line

x = d.width;

y += getVgap() + rowh;

rowh = d.height;

}

}

}

return new Dimension(maxwidth, y+rowh+getVgap());

}

}

}

My problem now, is that the first layout given by this layout manager gives much too much vertical space to my panels. But as soon as I resize the window, everything comes out perfectly, exactly the way I want. Do you know what I can do to solve that first-display glitch? Or maybe your newest version of WrapLayout works without problems, but I haven't been able to find it... (searching for WrapLayout return hundreds of pages where you tell people to search for WrapLayout ;-) )

Anyway, thanks for the pointer

npigueta at 2007-7-8 2:09:56 > top of Java-index,Desktop,Core GUI APIs...
# 3

> searching for WrapLayout return hundreds of pages where you tell people to search for WrapLayout ;-) )

That just goes to show that this question is frequently asked. If people would search the forum first before asking questions to see if their question has already been asked then we wouldn't have this problem :)

Also, by having you search it forces you to read the postings which is how you found the other LayoutManager.

> Or maybe your newest version of WrapLayout works without problems,

If you downloaded it from the link you found with the BetterFlowLayout, then it should be the most recent.

> Your WrapLayout seems to work, but has some strange glitches on the border

That doesn't describe the problem very well. Notice how my example had actual code you could use to test it?

camickra at 2007-7-8 2:09:56 > top of Java-index,Desktop,Core GUI APIs...
# 4

> Your WrapLayout seems to work, but has some strange

> glitches on the border

>

> That doesn't describe the problem very well. Notice

> how my example had actual code you could use to test

> it?

nevermind, I seem to have fetched another class from somewhere else that happened to have the same name.

Anyway, my current problem is the following, when the layout's preferred size is requested, the target component doesn't yet have it's final size (it's size is still (0,0)), which means that the layout size is not computed properly, it will calculate the size for a target component of width 0, thus placing only one child component on each line. Because it does this, the preferredLayoutSize() method returns a size that is much too high...

In general it seems that when the preferredLayoutSize() method is called, the target component still has it's pre-layouting size, which makes total sense. The problem is that preferredLayoutSize() will return a size that is relevant to the current (pre-layouting) size of its target component, which also means that the computed size is not suited to the size the target component will really get to be after the layout is complete.

The result of this is that when you make a big window resizing, the target component will either appear too high and leave some blank space under the wrapped children components (if you made the window bigger), or not high enough and hide some children component (if you made the window smaller).

Is there any solution to this problem? By what I could find, it seems that this layout manager would be the only one that has to rely on it's target component current size to determine its own size...

I'll post a short self contained compilable and executable snippet a bit later ;)

In the mean time, if you have any idea, please share it.

Thanks for your help

npigueta at 2007-7-8 2:09:56 > top of Java-index,Desktop,Core GUI APIs...
# 5

import java.awt.event.*;

import java.awt.*;

import javax.swing.*;

public class Example {

public static void main(String[] args){

JFrame mainFrame = new JFrame();

mainFrame.setLayout(new BorderLayout());

JPanel buttonPane = new JPanel();

addResizeButton(buttonPane, 320, 240, mainFrame);

addResizeButton(buttonPane, 640, 480, mainFrame);

addResizeButton(buttonPane, 800, 600, mainFrame);

mainFrame.add(buttonPane, BorderLayout.NORTH);

Box container = new Box(BoxLayout.Y_AXIS);

for(int i = 0; i < 4; i ++){

addSection(container);

}

mainFrame.add(new JScrollPane(container), BorderLayout.CENTER);

mainFrame.setSize(320, 240);

mainFrame.setVisible(true);

}

private static void addResizeButton(JPanel buttonPane, final int width, final int height, final JFrame target){

JButton button = new JButton(width + "x" + height);

button.addActionListener(new ActionListener(){

public void actionPerformed(ActionEvent e){

target.setSize(width, height);

}

});

buttonPane.add(button);

}

private static void addSection(Box container){

Box section = new Box(BoxLayout.Y_AXIS);

for(int i = 0; i < 4; i ++){

addWrappingPane(section);

}

section.setBorder(BorderFactory.createLineBorder(Color.BLUE));

container.add(section);

}

private static void addWrappingPane(Box section){

JPanel wrappingPane = new JPanel();

wrappingPane.setLayout(new BetterFlowLayout());

for(int i = 0; i < 30; i ++){

JLabel label = new JLabel("label" + i);

label.setBorder(BorderFactory.createLineBorder(Color.RED));

wrappingPane.add(label);

}

wrappingPane.setBorder(BorderFactory.createLineBorder(Color.GREEN));

section.add(wrappingPane);

}

private static class BetterFlowLayout extends FlowLayout {

public BetterFlowLayout() {

super();

}

public BetterFlowLayout(int align) {

super(align);

}

public BetterFlowLayout(int align, int hgap, int vgap) {

super(align, hgap, vgap);

}

@Override

public Dimension preferredLayoutSize(Container target) {

return betterPreferredSize(target);

}

@Override

public Dimension minimumLayoutSize(Container target) {

return betterPreferredSize(target);

}

public Dimension betterPreferredSize(Container target) {

synchronized (target.getTreeLock()) {

Insets insets = target.getInsets();

int maxwidth = target.getWidth() - (insets.left + insets.right + getHgap()*2);

int nmembers = target.getComponentCount();

int x = 0, y = insets.top + getVgap();

int rowh = 0;

for (int i = 0 ; i < nmembers ; i++) {

Component m = target.getComponent(i);

if (m.isVisible()) {

Dimension d = m.getPreferredSize();

m.setSize(d.width, d.height);

if ((x == 0) || ((x + d.width) <= maxwidth)) {

// the component fits on the current line

if (x > 0) {

x += getHgap();

}

x += d.width;

rowh = Math.max(rowh, d.height);

} else {

// we need to put the component on a new line

x = d.width;

y += getVgap() + rowh;

rowh = d.height;

}

}

}

System.out.println ("Target component size: (" + target.getWidth() + ", " + target.getHeight() + ")");

System.out.println ("Returned preferred size: (" + maxwidth + ", " + (y+rowh+getVgap()) + ")");

System.out.println ();

return new Dimension(maxwidth, y+rowh+getVgap());

}

}

}

}

OK. I think this should be enough to illustrate the problem, and it mimics my situation pretty closely. Look at the console output from the layout manager.

npigueta at 2007-7-8 2:09:56 > top of Java-index,Desktop,Core GUI APIs...
# 6
Dare I ask again if anyone can help me find out a way to avoid this weird "calculate the preferred size based on the old size" behavior?Thanks in advance for your help.
npigueta at 2007-7-8 2:09:56 > top of Java-index,Desktop,Core GUI APIs...
# 7

Nevermind, I found a solution myself. It's not perfect yet, but it behaves a lot better than simply using an extension of FlowLayout, as it solves most of my problems. Here is my new WrapPanel class. Try using it instead of a regular JPanel in the Example class I posted earlier in this topic, and you will see the difference.

import javax.swing.*;

import java.awt.*;

public class WrapPanel extends JPanel {

int currentWidth = 0;

public WrapPanel(int direction){

super(new BetterFlowLayout(direction));

}

@Override

public void setLayout(LayoutManager manager){

if(!(manager instanceof BetterFlowLayout)){

throw new IllegalArgumentException("WrapPanel's layout manager can only be a BetterFlowLayout");

}

super.setLayout(manager);

}

@Override

public void setBounds(int x, int y, int width, int height){

super.setBounds(x, y, width, height);

this.checkPreferredSize(width);

}

@Override

public void setSize(Dimension d){

super.setSize(d);

this.checkPreferredSize(d.width);

}

@Override

public void setSize(int width, int height){

super.setSize(width, height);

this.checkPreferredSize(width);

}

private void checkPreferredSize(int width){

if(width != this.currentWidth){

this.currentWidth = width;

((JComponent)this.getParent()).revalidate();

}

}

private static class BetterFlowLayout extends FlowLayout {

public BetterFlowLayout() {

super();

}

public BetterFlowLayout(int align) {

super(align);

}

public BetterFlowLayout(int align, int hgap, int vgap) {

super(align, hgap, vgap);

}

@Override

public Dimension preferredLayoutSize(Container target) {

return betterPreferredSize(target);

}

@Override

public Dimension minimumLayoutSize(Container target) {

return betterPreferredSize(target);

}

public Dimension betterPreferredSize(Container target) {

synchronized (target.getTreeLock()) {

Insets insets = target.getInsets();

int maxwidth = target.getWidth() - (insets.left + insets.right + getHgap()*2);

int nmembers = target.getComponentCount();

int x = 0, y = insets.top + getVgap();

int rowh = 0;

for (int i = 0 ; i < nmembers ; i++) {

Component m = target.getComponent(i);

if (m.isVisible()) {

Dimension d = m.getPreferredSize();

m.setSize(d.width, d.height);

if ((x == 0) || ((x + d.width) <= maxwidth)) {

// the component fits on the current line

if (x > 0) {

x += getHgap();

}

x += d.width;

rowh = Math.max(rowh, d.height);

} else {

// we need to put the component on a new line

x = d.width;

y += getVgap() + rowh;

rowh = d.height;

}

}

}

return new Dimension(maxwidth, y+rowh+getVgap());

}

}

}

}

npigueta at 2007-7-8 2:09:56 > top of Java-index,Desktop,Core GUI APIs...