Wrap Layout
Posted by Rob Camick on November 6, 2008
A layout manager has two main functions:
- determine the preferred size of the container
- layout the components in the container based on the layout rules
The FlowLayout is a strange animal. It does both of these functions. The preferred size of the container assumes all components will be laid out in a single row. The layout code will wrap components to the next row when the maximum width of the container is encountered. However, the problem is that the functions don’t talk to one another. When the components are wrapped to a new row, the preferred size doesn’t change so you never see the components on the extra row.
What we want is the preferred size to be dynamically calculated as the size of the container is changed. In other words, as the width of the container changes the height will need to be recalculated as well. The WrapLayout extends the FlowLayout to implement this functionality. This will result in synchronizing the preferred size of the container with the layout of the container.
In the following example, the button panel was added to the north of a BorderLayout and the blue panel added to the center. You use the WrapLayout the same as you would use the FlowLayout:
buttons.setLayout(new WrapLayout());
As the frame is resized smaller, the button panel will increase in height and the blue panel will decrease:
When the panel is added to a scroll pane, the size of the scroll pane won’t change, but horizontal and vertical scrollbars will appear as required.
The initial preferred size calculation of the layout manager still assumes all components will be displayed on a single row. So if you pack() a frame the preferred width may be excessive. You can limit the width of the container by using:
buttons.setSize(new Dimension(300, 1));
Note, you must use the setSize() method, this will still allow the preferred size to be dynamically changed.
Numain said
Great! Worked immediately. It’s already in use :-)
Rob Camick said
Glad you found it usefull.
Francois Daniels said
Very cool. Thank you, you saved me a lot of thinking!
Rob Camick said
Cool is good, thanks for the feedback.
maciej said
Thanks so much, almost works perfectly. One problem I encountered is that when the frame in which the wraplayout manager is used is maximized and then restored to the original size, the panel implementing the wraplayout is not being updated to the correct size. I worked around the issue by adding a component listener to the panel and triggering the revalidate() command on the panel.
Rob Camick said
Maciej,
Thanks for the feedback and your further information in the email. The problem happened when nesting panels. The update is now available for download.
Rob
Arni said
There is problem during maximaizing window and restore to orginal size. Something is
wrong, container is not repainted.
Rob Camick said
Works fine for me. Post your SSCCE that shows the problem on the “Contact Us” page and I’ll take a look to see what is different about your test case.
Nemi said
Thanks, this worked good for me. I also had a problem with the panel not revalidating after resizing, but took care of it by adding a componentAdapter to the panel like Maciej.
narayan said
Thanks man it’s cool I’ve got some ideas from this .. Finally i made dynamic Panel. Scollable..
John B. Matthews said
Exemplary! Minor typo: “layed” should be “laid”.
Rob Camick said
Thanks for the feedback. Only one typo, that has to be a record :)
John Tamplin said
Can you clarify the license on WrapLayout.java?
I would like to include WrapLayout.java from in Google Web Toolkit, which is licensed under Apache 2.0. However, there is no copyright notice or statement that it is public domain, and I couldn’t find anything on this site with any sort of blanket copyright statement.
Would it be acceptable to include this file in GWT licensed under Apache 2.0 (modified only to meet GWT style guidelines)?
Gil said
Thanks for writing wraplayout !! Just what I needed.
You mentioned above that placing the panel (having the wraplayout) within a scrollpane will not change the size of it but scroll bars will appear as needed.
I have an organizing panel (with gridbaglayout) and within it 2 panels, a and b. b itself contains a panel, c, and a button. I dinamically place checkboxes within panel c and it is defined as having a wraplayout. When I place a great amount of checkboxes in c, it grows and takes up all the horizintal space available for it within panel b (which is within the organizing panel) but as a result grows vertically and causes the entire organizing panel to grow vertically. This comes out very ugly. I therfore place panel c within a scrollpane and place the scrollpane within panel b. Now the scrollpane takes up no horizontal space at all and I can only see the vertical scroll bar. The checkboxes are not even visible. Only when I widen the entire frame do I see the checkboxes in 1 vertical line (when no scroll pane was used there were 3-4 checkboxes on each row that fitted without widening the frame.
Anyone got an idea why this is happening?
Rob Camick said
Nope. I don’t do very well with verbal descriptions. I need to see a SSCCE that demonstrates the problem. You can email me one through the “Contact Us” page.
softestpawn said
Nice one, thanks, does what it says in the classname
Rob Camick said
Thanks, it was one of the easier classes to name.
Rudy said
You made my day. Works like a charme. Thank you very much. Best Rudy
Rob Camick said
Glad I could help get the week of to a good start.
Andrew said
Thanks muchly! Worked exactly as expected.
Rob Camick said
Glad to hear this.
Anonymous said
You saved a lot of time for me thank you very much
Rob Camick said
Then reading this entry was time well spent. Thank for the feedback.
Anonymous said
great Job. M using it
Rob Camick said
Thanks.
Manda Bernstein said
Perfect , keep writing
Rob Camick said
Thanks. As long as I have ideas I will continue to write :)
Anonymous said
Why, oh why didn’t I find this earlier! Just exactly what I needed.
Thank you
bb-generation said
Thanks for writing this nice piece of code :)
Just one question: What are the terms (the license) of this class?
Rob Camick said
You can use/modify the code however you wish.
Anonymous said
Thank you it works great!
Anonymous said
Thanks for writing this nifty code. I have a number of buttons ,in a button group, in WrapLayout Panel (has a Border) that is added to a BorderLayout North Panel which is added to a horizontal split pane . When I move the divider of split pane the last button drops a row as expected but the others do not. If i release the mouse , I can then move the divider from the last position and it will drop the next button to the next row, etc until all the buttons are now in column. From that position I can move the divider and the buttons move smoothly from vertical to horizontal alignment as long as the mouse moving the divider is held down. This is the action I was originally hoping for. When the mouse is released from the divider I am back to having to successively move the divider as many times as there are buttons in order to get all the buttons to wrap. I have looked at your code but the truth is I don’t even know where to start. Any advice would be appreciated. Thanks.
Rob Camick said
If you email an SSCCE to me using the Contact Us page I can take a look.
Anonymous said
As per Robs suggestion, setting the panel with the WrapLayout which was in a JSplitFrame as follows :
panel.setMinimumSize( new Dimension(0, 0) );
resolved the problem and everything works great
Shant said
Thank you for posting WrapLayout! I’ve been always using MigLayout, which basically supports ‘everything’ except ‘wrapping’!
Just one concern: You mentioned in your reply to Maciej above that an update is available for download, which solves the problem of “nesting panels”. However, using the version available in the link provided on this page, I’m still facing this problem of “panel not revalidating after resizing”.
Rob Camick said
If you create an SSCCE and email it to me using the “Contact Us” I can take a look to see if I can find a problem. I don’t use Mig layout so the SSCCE should just use standard layout managers to demonstrate the problem.
bonfa said
Thank you! It works perfectly!
Rob Camick said
Glad it does what you require.
esk3 said
Thank you so much! It’s great!
Rob Camick said
Glad you like it.
AC said
Currently downloaded file contains some Chinese characters instead of code.
Rob Camick said
It downloads fine for me. It should just be a simple ascii file. It must be your browser or the file encoding you are using. I don’t know how you would change these settings.
AC said
You are indeed correct. It is strange because its first time that it happened for me. Sorry for that and thanks a lot for response and the code.
MagNet said
Thank you! Works like a charm, needed to dynamically change layout of a component inside a scrollpane and this did just the trick.
Anonymous said
Excellent! Worked perfectly here :)
kaefert said
thanks for publishing! great find! wonder why the default FlowPanel doesn’t behave like that!
Anonymous said
Thank you very much for that great chunk of code.
However, I wonder about “private Dimension preferredLayoutSize;” – is it ever used?
Anonymous said
Hi, this is a great work! But I found one possible issue. If I turn off “show window contents while dragging” in the “visual effects” of Windows 7, and then when I resize the window from big to small, it is highly possible that the wrap will not happen. That’s to say, there is no new line and the right part of the line is truncated.
I found this problem because I run my program correctly on windows but not on Solaris. Then, I tried to find the difference and reproduced it on Windows.
Do you have any idea why this can happen?
zpf7879@126.com
Rob Camick said
Not sure and I can’t test it. Maybe turning off the visual effects while dragging causes Swing not to do redo the layout of a window when it is resized. This doesn’t really make sense since dragging a window is different from resizing a window.
Anonymous said
Awesome, worked like wonder
Tarmo said
Works great. Thank you for sharing this !
Tim Abell said
I’ve created a github repo and jar file download for convenience. https://github.com/timabell/WrapLayout
Anonymous said
Rob thank you so much for making this!!!
Aleksandr Zaigrayev (@dw_3105) said
Really helpful class, thank you!
Rob Camick said
Glad you found the class helpful.
Sam said
Thank you so much for sharing :D, Excellent code!
Rob Camick said
Thanks for the comments.
Mike Trad said
This works great! The only two issues I’m having with a parent scrollpane is that the WrapLayout panels end up with a smaller width than the scrollpane container, kinda like a right and left margin. Also, even if I repaint only in the end of the loop (the loop adds labels to the wraplayout panel) the labels are being showed first like a normal FlowLayout (one singe line) then they rearrange one by one, like an animation.
Dave said
Those look like GridBagLayout behavior. You need to set some GridBagConstraints to fix that.
I for one have one issue: looks like I HAVE TO set a preferred or maximum size if I want it to work on a panel inside a JScrollPane. I’m still trying to figure out how to make it flow only horizontally (obeying the current height of the JScrollPane) or only vertically (obeying the current width of the container JScrollPane)
Rob Camick said
WrapLayout is designed to obey the width of the panel. Then it will wrap. So you horizontal requirement will never work. The vertical requirement is already supported, I don’t know what you are doing wrong.
Dave said
I made this SSCCE of the problem (still not exactly the problem, because on this example, if we keep resizing the window, the layout will adjust. on my real application, not even this can be done).
Maybe it has to do with the height of the Jpanel “holder”
http://pastebin.com/DXbRRWX6
P.S. yeah, there’s only the “vertical requirement”. The horizontal I mentioned makes no sense.
Rob Camick said
The preferred size is base on a single row of components. You can give a suggestion for the width by specifying the size(?, 1) of the panel. On your example, when I specify the size I can then increase the width of the frame and wrapping works. However, I can’t shrink the frame because the scrollbar appears. I’m not sure why this happens with the GroupLayout. So in your example I used my ScrollablePanel (which you can find on this site) to force the width of the panel to be the same as the width of the viewport. Then wrapping works when the width increases or decreases. Here is the change I made to your code:
Note: I also made a 1 line change to WrapLayout to better support the panels preferred size when you first pack() the frame.
Dave said
(can’t reply to you last post so I’ll reply here)
My last try before moving to a non-flow approach would be resize the panel based on the viewport. Your ScrollablePanel is great on doing that. Also, of course, the size hint I was missing. Thanks!
I still see some certain events that do not get update, for example:
1) Run the code with the new modifications and maximize the window. The width is increased but the height does not decrease. http://imgur.com/wsD7FVA
2) Now restore it, width does not decrease, and no scrollbars. http://imgur.com/gnfMG7e
3) Now resize it horizontally, the items will wrap in sequence for every bit of resizing, until finally all of them are visible
Another event I observe is when you shrink it horizontally too fast, the items won’t wrap as well.
Any comments on that? Or this is out of the scope of the WrapLayout?
Rob Camick said
panels end up with a smaller width than the scrollpane – thats the point of the WrapLayout. The row will automatically wrap when it get too wide. Therefore the width will always be less than or equal to the width of the scrollpane. Not sure I understand your animation comment. Components are only laid out when the panel is revalidated().
Sterling said
Out of curiosity, is there a way at all to create a “new line” or line break in the items before it has fully filled the width of the container but still respect the left, right, or centered alignment of the layout? I dont know think anything like that is included but if you have an idea that I could look into coding I’d greatly appreciate.
Or, is the best way just to have a vertical box layout with a series of wrap layout panels?
Rob Camick said
WrapLayout just calculates a preferred size for the panel. It doesn’t do the actual layout, that is still done by the FlowLayout. So there is no way to change the WrapLayout to do what you want.
I would guess a vertical BoxLayout is the way to go.
Dave said
Looks like my latest post was marked as spam, so I’ll try again without the links:
My last try before moving to a non-flow approach would be resize the panel based on the viewport. Your ScrollablePanel is great on doing that. Also, of course, the size hint I was missing. Thanks!
I still see some certain events that do not get update, for example:
1) Run the code with the new modifications and maximize the window. The width is increased but the height does not decrease.
2) Now restore it, width does not decrease, and no scrollbars.
3) Now resize it horizontally, the items will wrap in sequence for every bit of resizing, until finally all of them are visible
Another event I observe is when you shrink it horizontally too fast, the items won’t wrap as well.
Any comments on that? Or this is out of the scope of the WrapLayout?
Dave said
Looks like it’s working now after I changed to a GridBagLayout (one column) instead of a GroupLayout or a BoxLayout for the “holder” panel. The three of them are capable of stacking the panels top to bottom, but only GridBagLayout seems to work for quick resizings:
GridBagLayout layout = new GridBagLayout();
holder.setLayout(layout);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx=0;
gbc.gridy=0;
gbc.weightx=1;
gbc.weighty=1;
gbc.fill=GridBagConstraints.HORIZONTAL;
holder.add(firstPanel, gbc);
gbc.gridy=1;
holder.add(secPanel, gbc);
My problem is solved, but I figure you would want to know about this weird behavior for GroupLayout and BoxLayout. And I volunteer for any further tests :)
Rob Camick said
Dave, I sent you an email last night with my findings. I didn’t try GridBagLayout, but it looks like its hit and miss whether this code will work when using nested panels. Glad you got something to work for your requirement.
Anonymous said
Thank for sharing the code.
TT said
Any reason for the “synchronized (target.getTreeLock())” in your layoutSize method? AFAICT UI should in general be coded as single-threaded, ie code that modifies the container should run on the UI thread rather than have it synchronize on a lock of some sort.
Rob Camick said
WrapLayout is based on FlowLayout. I just copied that piece of code from the FlowLayout class. I agree UI changes should be done on the EDT. FlowLayout is an old AWT class, so maybe that code isn’t needed anymore when using Swing, but I left it in since this class could be used in an AWT app.
Anonymous said
Thanks for sharing this solution!
I realized that FlowLayout doesn’t recalculate the size and I found the perfect solution right here.
David V said
I was using a FlowLayout before and couldn’t get this to work. I made a quick change to using WrapLayout and voila!
Thanks a lot
John Gowers said
Rob, thanks so much for making this. Is there a package that I can import this class from? What is the licence on this code?
Rob Camick said
There is no package, you are free to add the class to any package you wish. As the “Contact Us” page says:
It should be noted that none of the code presented here is used in any real application and therefore you use it at your own risk. You are free to use and/or modify any or all code posted on the Java Tips Weblog without restriction. A credit in the code comments would be nice, but not in any way mandatory.
PeterVermont said
Thanks!
There appears to be a null character at the beginning of line 54: “* @return the preferred dimensions to lay out the”
Rob Camick said
Thanks. Character has been removed.
miterada said
Thank you!
One point:
The width of the container using WrapLayout is expanded to the width of the largest component of the container.
This is reasonable in order to make all the components visible, but when I put the container in a JScrollPane, a horizontal scroll bar appears.
Sometimes it is OK to trim wide components (for example a button with lengthy label may be distinguishable by the beginning of the label).
In addRow method, updating the dim.width only when the number of components in the row is greater than one will achieve the trimming of wide components.
Rob Camick said
The WrapLayout is just an extension of the FlowLayout. All components should be displayed at their preferred size which means the preferred size of the panel should be such to display all components. If you have a special requirement then you can customize the logic as you see fit, but I don’t think this requirement should be part of the default bvehaviour for the WrapLayout.
miterada said
Thank you for your reply. I totally agree with you. My post was just for someone who doesn’t like horizontal scroll bars :)
Anonymous said
Thanks very mutch!
Fredrik Wigert said
Hello! Many thanks for this layout, it’s really covering up something missing in swing.
I have one question though, why does this line exist?
if (targetWidth == 0)
targetWidth = Integer.MAX_VALUE;
I had issues when adding 200 buttons to a panel, it took maybe 10 seconds of mad flickering before it looked good.
If I put a reasonable value instead of MAX_VALUE, it took maybe 1 second, and not much madness going on during that time.
I’m not sure how this affects a panel in a scrollpane though…
Maybe my problem is that targetwidth is 0 in the first place. Not sure why that is.
Thanks anyway! :)
Rob Camick said
I just tried this with 200 buttons on a panel and with a panel added to a scrollpane. In both cases the frame displayed instantly without any flickering. I use JDK7_19 on Windows 7. Maybe this is a version/platform issue? I used Integer.MAX_VALUE as an easy value. The idea of that code is to allow all the components to be displayed on a single line when the width of the parent frame has not yet been determined, so value of 0 is normal when the frame is first packed.
Fredrik Wigert said
I have images in all my buttons… Don’t know if it matters.
Maybe it’s not a general problem, but if anyone else runs into it, maybe they should try trimming targetWidth too.
Anonymous said
Great! Thanks a lot for this. It solves this type of problem in an instant.
Manikanta said
Excellent, working as I wanted and expected. Great Work
Rob Camick said
Glad it does what you wanted and expected.
Shan said
Thx still useful :)
Anonymous said
Thank you very much for this code! Basically it does excatly what I was looking for. However I think this layout has an performance issue if there are many components in it. Try this:
…constructor of some JFrame with BorderLayout…:
JPanel panel = new JPanel(new WrapLayout(FlowLayout.LEFT, 0, 0);
addText(“hj sdhjsdf kfh jksdfh fsdjkf kfjsdh …… hjkf sdjkhfsd sjksdfh”);
JScrollPane scrollPane = new JScrollPane(panel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
add(scrollPane, BorderLayout.CENTER);
…
public void addText(String text) {
String[] words = text.split(” “);
for (String word : words) {
JLabel label = new JLabel(word + ” “);
panel.add(label);
}
}
What you see, is that you can almost watch the components being laid out. This is not the case if plain FlowLayout ist being used.
Rob Camick said
I have tested with over 100 components and don’t see a problem. I use Windows 7 and have tested on various versions of the JDK.
Anonymous said
I still see myself as a newbie in java, your effort is appreciated, I wish it is added to the Library. Thank you.
Anonymous said
Thank you so much! I really apreciate it!
simondass said
Thanks, worked well.
For those that have problems with maximize/restore resizing try forcing a validate on the components after the maximize/restore like this:
Rob Camick said
Thanks for the feedback. I have never notice a problem when maximizing/restoring a frame. I would suggest you don’t need to sleep because the code is executing on the EDT so this will just cause the EDT to sleep. All you should need is the invokeLater() since adds the logic to the end of the EDT allow other code on the EDT to finish executing. Also when using Swing you should just use revalidate() on the panel. No need for invalidate() and validate().
simondass said
Good point re using revalidate(), thanks.
Anonymous said
Thank you !!
buzz said
Thanks.. It worked fine for me
K said
This is the best layout to use with DragAndDrop! Thank’s!
Naitoreivun said
It’s great! Thanks! :D
Anonymous said
Awesome!
Ofer said
Great Layout Manager!
Only problem I’ve experienced is that on Windows,
When applying wraplayout to a JToolBar, the setSize() function only changes
the width down to a certain point ~120px. I need the width of the toolbar to be 60px.
In OSX this works fine…
Any advice?
Rob Camick said
That has nothing to do with the WrapLayout. That is the minimum size of the frame because of the Window decorations (border, close, iconify and exit buttons).
Ofer said
Thanks for pointing me in the right direction :)
Anonymous said
Great work!
I thing there is a little bug. At line 127
if (rowWidth + d.width > maxWidth) {
hgap should be taken into account, as it is later on added to rowWidth.
千晃 said
This solved my problems with FlowLayout in a ScrollPane. Thank you very much for making this nice piece of code public, I’ll use it in future projects.
Anonymous said
Still works great. Thanks
Anonymous said
Can you please license this under a FLOSS license and put it on github.com. People are both contributing to your code and it’s not being use and are commenting non-constructively (“thank you”, etc.) which should be removed.
Anonymous said
Thank you very much! It’s great.
John Greene said
this is what I was looking for. It’s a wonder that it isn’t in standard Java. Thank you very much.
john
Darwin said
excelente layaout, no me imagine que esta fuera la solucion a mi problema, muchas gracias amigo y saludos desde Ecuador
Anonymous said
Nice!
Anonymous said
good and thank you
Hossein said
Thanks for sharing this solution! It is working like a watch :)
Anonymous said
Thanks so much!
waleed kilany said
Thanks, you saved so much hassle
Anonymous said
Thanks for the work done !
You saved my day and many others.
You’re the exact person every programer should take example on.
Thanks for helping the comunity !
Anonymous said
Thank you verry much. It helped a lot!
Jörg said
Another time saving solution by Rob.
Thank you so much.
Stefan Reich said
Nice but I can’t get it to work in a vertical scrollpane
Rob Camick said
Try it with the ScrollablePanel: https://tips4java.wordpress.com/2009/12/20/scrollable-panel/
Stefan Reich said
Thanks. I made my own layout manager in the meantime :)