2013-02-26: There is also a scala version!
Problem Statement and Approach
Sometimes you design some parts of Swing applications manually. In my case, it happens mostly for the higher level layout of my applications or more recently in an application written in Scala as an experiment. In this context, it often happens that you nest multiple JPanels, each using BorderLayout, to successively push particular components in different directions. I remember from one of my Tcl/Tk courses that such a layout existed. After a quick internet search, it is still documented on the Tcl/Tk website.
In the past, I used a builder that would automatically nest multiples JPanels to emulate this functionality. I was using a builder pattern that was providing a really convenient API to create the layout. However, the actual hierarchy of components was still really deep and removing components from the layout was un-intuitive (it requires to use either the builder or another utility function).
So, after years of believing that implementing a Swing layout was complicated, I just had a look at some of the existing layouts. And guess what! It is not that difficult. So I implemented as a useful exercise the PushBorderLayout class that provides the same kind of layout as nested panels with BorderLayouts but in a flatter component tree.
The PushBorderLayout
From a user point of view, the PushBorderLayout is a layout as all others. It is used in the same way as a BorderLayout but it allows any number of "add" until an "add" is done at the CENTER position. It reuses some names of the constants used by the BorderLayout. However only the ones compatible with both left-to-right and other languages are used. Here is an example usage:
Container c = ...; c.setLayout(new PushBorderLayout()); c.add(c0, PushBorderLayout.LINE_START); c.add(c1, PushBorderLayout.LINE_START); c.add(c2, PushBorderLayout.LINE_END); c.add(c3, PushBorderLayout.PAGE_START); c.add(c4, PushBorderLayout.LINE_START); c.add(c5, PushBorderLayout.PAGE_END); c.add(PushBorderLayout.pad(10), PushBorderLayout.PAGE_END); c.add(c6, PushBorderLayout.PAGE_END); c.add(c7, PushBorderLayout.LINE_END); c.add(PushBorderLayout.pad(10), PushBorderLayout.LINE_END); c.add(c8); // or c.add(c8, PushBorderLayout.CENTER);
The above code also used the "pad" utility method that created an invisible component. You can also used the "static import" feature of Java if you want not to repeat "PushBorderLayout" everywhere.
In the above example, consider the components c0 to c8 as being labels with black borders. Then you will get, respectively with the “preferred size” and after resizing the window:
Getting it!
You can get the PushBorderLayout either as a ready to use jar file or on obtain the sources from the project on github (Java) (or the scala one).
Implementation Hints
Implementing a AWT or Swing layout is done by implementing the "java.awt.LayoutManager2" interface. Firstly, it consists in providing a way to add and remove components to the container. Secondly, the layout manager must be able to process and affect sizes to the child components. Also it must be able to process a minimum size and a preferred size based on the ones of the child components.
Because I'm lazy, the layout manager does not really honor the “getMinimumSize” contract and just returns (0,0). The “getPreferredSize”, that I consider more useful, is implemented.
A bug? Missing features? Some feedback or remarks? Contact me at click-me ;-p @nospam.com.