Java Tips Weblog

  • Blog Stats

    • 1,301,166 hits
  • Categories

  • Archives

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());

wrap-layout

As the frame is resized smaller, the button panel will increase in height and the blue panel will decrease:

wrap-layout2

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.

Get The Code

WrapLayout.java

Related Reading

How to Use Flow Layout

About these ads

97 Responses to “Wrap Layout”

  1. Numain said

    Great! Worked immediately. It’s already in use :-)

  2. Rob Camick said

    Glad you found it usefull.

  3. Francois Daniels said

    Very cool. Thank you, you saved me a lot of thinking!

  4. 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

  5. Arni said

    There is problem during maximaizing window and restore to orginal size. Something is
    wrong, container is not repainted.

  6. 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.

  7. narayan said

    Thanks man it’s cool I’ve got some ideas from this .. Finally i made dynamic Panel. Scollable..

  8. 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)?

  9. 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?

  10. Nice one, thanks, does what it says in the classname

  11. Rudy said

    You made my day. Works like a charme. Thank you very much. Best Rudy

  12. Andrew said

    Thanks muchly! Worked exactly as expected.

  13. Anonymous said

    You saved a lot of time for me thank you very much

  14. Rob Camick said

    Then reading this entry was time well spent. Thank for the feedback.

  15. Anonymous said

    great Job. M using it

  16. Perfect , keep writing

  17. Anonymous said

    Why, oh why didn’t I find this earlier! Just exactly what I needed.

    Thank you

  18. bb-generation said

    Thanks for writing this nice piece of code :)

    Just one question: What are the terms (the license) of this class?

  19. Anonymous said

    Thank you it works great!

  20. 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

  21. 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.

  22. bonfa said

    Thank you! It works perfectly!

  23. esk3 said

    Thank you so much! It’s great!

  24. 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.

  25. 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.

  26. Anonymous said

    Excellent! Worked perfectly here :)

  27. kaefert said

    thanks for publishing! great find! wonder why the default FlowPanel doesn’t behave like that!

  28. Anonymous said

    Thank you very much for that great chunk of code.
    However, I wonder about “private Dimension preferredLayoutSize;” – is it ever used?

  29. 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.

  30. Anonymous said

    Awesome, worked like wonder

  31. Tarmo said

    Works great. Thank you for sharing this !

  32. Tim Abell said

    I’ve created a github repo and jar file download for convenience. https://github.com/timabell/WrapLayout

  33. Anonymous said

    Rob thank you so much for making this!!!

  34. Really helpful class, thank you!

  35. Sam said

    Thank you so much for sharing :D, Excellent code!

  36. 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:

        firstPanel.setSize(500, 1);
        secPanel.setSize(500, 1);
        // JPanel holder = new JPanel();
        ScrollablePanel holder = new ScrollablePanel();
        holder.setScrollableWidth( ScrollablePanel.ScrollableSizeHint.FIT );
        

        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().

  37. 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.

  38. 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.

  39. Anonymous said

    Thank for sharing the code.

  40. 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.

  41. Anonymous said

    Thanks for sharing this solution!

    I realized that FlowLayout doesn’t recalculate the size and I found the perfect solution right here.

  42. 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

  43. 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.

  44. PeterVermont said

    Thanks!

    There appears to be a null character at the beginning of line 54: “* @return the preferred dimensions to lay out the”

  45. 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 :)

  46. Anonymous said

    Thanks very mutch!

  47. 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.

  48. Anonymous said

    Great! Thanks a lot for this. It solves this type of problem in an instant.

  49. Manikanta said

    Excellent, working as I wanted and expected. Great Work

  50. Shan said

    Thx still useful :)

  51. 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.

  52. 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.

  53. Anonymous said

    Thank you so much! I really apreciate it!

  54. 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:

    addWindowStateListener(new WindowStateListener() 
    {
    
    	@Override
    	public void windowStateChanged(WindowEvent e) 
    	{
    		try {
    			// Small pause to allow component sizes to get up to date
    			Thread.sleep(10);
    		} catch (InterruptedException e1) {
    			// TODO Auto-generated catch block
    			e1.printStackTrace();
    		}
    		SwingUtilities.invokeLater(new Runnable() {
    			@Override
    			public void run() {
    				invalidate();
    				// These use WrapLayout
    				longToolbar.invalidate();
    				shortToolbar.invalidate();
    				validate();
    			}
    		});
    	}
    });
    
    • 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().

  55. Anonymous said

    Thank you !!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

Join 97 other followers

%d bloggers like this: