Multi-border layout

the inception story

The story of this layout started on a misconception: one managers and some not-so-much-swing-aware developers of a small development team somewhere back in year 2000 thought that java GUI was slow because a typical swing GUI is a composite made of several panels. In particular, when you need a titled or lined border around some components somewhere inside a panel, you need to instantiate a sub-panel. So they thought about a marvelous panel (along with its layout manager) able to hold any combination of borders and components. They then continue their brainstorm and think about a lot of marvelous behaviour for this panel: dynamism (you can easily add/remove show/hide and component inside the layout with a kind of declarative constraints), easy interface (you have constraints like ADD_ON_LEFT_OF_THE_LAST_ONE), background calculation of the layout (again for performance) ... The result was a horrible enormous non-maintainable monster. So, they call a experienced swing developer to the rescue (me). Despite all the effort put to fix it, the initial panel and layout manager were deprecated and abandoned. It was mainly due to the fact that we were not allowed to change its initial flawed interface and behaviour.

Surprisingly, I found that this component (designed with a total lack of knowledge on how swing works) had interesting properties. I also wanted to go over my frustration and prove to myself that I could made it run if allowed. So I restarted, at home, an implementation from scratch keeping only the interesting ideas ... it resulted to the MultiBorderLayout.

Functionalities

The MultiBorderLayout works mostly like the GridBagLayout: it uses cells with position, size, weight, alignment and insets

But there are some little differences:

  • You can add several components in the same cell, they are then laid-out as if there is a small flowLayout inside the cell. * You can pass null insets (interpreted as Insets(0,0,0,0)) * There's is no padding (because I never felt the need of that) * The associated constraint (MultiBorderConstaint) has a more user-friendly interface (to my opinion). * It uses only the preferred size of the components (and not the minimal size).

And, mainly, the MultiBorderLayout manages special border objects that can be put around any set of cells. Those borders can contain components (usually little control buttons) to perform actions on their content. A border is usually a good functional place to place behaviour inside a panel because it demarcates group of components that render 'logical' objects (that map to java objects if your design is good). The border is then a best place to put controls for actions applying on those objects. The actions provided by default is to enable/disable or show/hide the content. If you write your own borders, you can use any component and perform the actions you want.

As a result, this layout is not more complex to use than the usual GridBagLayout but allows building new kind of swing GUIs. These are GUIs where borders are first-class citizen (they can have components and behaviour).

Some screenshots

Lets demonstrate it visually. None of the layout demonstrated below can be achieved with usual swing layout managers.

  • Nested borders: You can see that as there is only one layout manager (and one panel) because all the labels are right-aligned, which is impossible otherwise without fixed-size components.
  • Crossing borders: This is more a funny border effect, but it proves that both borders are effectively on the same panel. If you try that with dynamic (collapsing) borders you will probably go into troubles...
  • Collapsing borders: borders with little control components inside allowing to expand/collapse them. Try the demo to see it working.

All those screenshots are taken from the unit-test available form the source code. TODO: some ref. on how to access and run the demo.

Layout of borders

Layout strategy is first to layout the cells without taking the borders into account (just like a GridBagLayout). After that, extra space is inserted between the line and columns of cells to make room for the borders.

For example, let's take a simple grid of 3 x 4 labels of equals size (the cells boundaries are highlighted in red):

If we keep the exact same layout but simply adding borders, you see that the layout simply reserve space for borders between the cells. The cells are kept aligned, so some holes can appear like between "cell 2.1" and "cell 3.1".

Note: those screenshots were made using the debug facility of the MultiBorderLayout. If you set the "debugMode" static flag to true at the startup of your program like this:

 
        MultiBorderLayout.debugMode = true;
 

then the MultiBorderLayout paints the cells indexes and boundaries in red to help you figure out what it is doing.

How to use it

The basic usage of the MultiBorderLayout is the same as the GidBagLayout. Like GidBagLayout, MultiBorderLayout has an associated constraints object names MultiBorderConstraints. Like for GidBagLayout you can modify and reuse the same MultiBorderConstraints object to add several components.

The MultiBorderConstraints has, in addition, some special setter methods that modifies a set of values and return the constraint itself. It allows to reuse the same constraint in an elegant way like following:

 
  (with GidBagLayout)
  
        GridBagConstraints gbConstr=... (reused)
        gbConstr.gridx=3;
        gbConstr.gridy=4;
        myPane.add(myComponent, gbConstr);
 
  (with MultiBorderLayout)

        MultiBorderConstraints mbConstr=... (reused)
        myPane.add(myComponent, mbConstr.setConstraints(3, 4));
 

You can see, in following example that reusing constraints along with the usage of the setConstraints(x, y) setter leads to concise and easy to read code.

 
        JPanel testPane = new JPanel(new MultiBorderLayout());
        
        MultiBorderConstraints labelConstr = new MultiBorderConstraints();
        labelConstr.anchor = MultiBorderConstraints.EAST;

        MultiBorderConstraints textfieldConstr = new MultiBorderConstraints();
        textfieldConstr.anchor = MultiBorderConstraints.WEST;
        textfieldConstr.insets = new Insets(2, 6, 0, 0);

        testPane.add(new JLabel("Label 1:"), labelConstr.setConstraints(0, 0));
        testPane.add(new JTextField(5), textfieldConstr.setConstraints(1, 0));
        testPane.add(new JLabel("Label 2:"), labelConstr.setConstraints(0, 1));
        testPane.add(new JTextField(10), textfieldConstr.setConstraints(1, 1));
        testPane.add(new JLabel("Long Label 3:"), labelConstr.setConstraints(0, 2));
        testPane.add(new JTextField(12), textfieldConstr.setConstraints(1, 2));
        testPane.add(new JLabel("Label 4:"), labelConstr.setConstraints(0, 3));
        testPane.add(new JTextField(8), textfieldConstr.setConstraints(1, 3));
 

The setConstraints(...) setter has various overloaded versions to allow easy modification of the most commonly used parameters.

Adding borders

The borders used by MultiBorderLayout are instances of the MultiBorder class. The rendering of a MultiBorder is done by a regular swing border contained in the MultiBorder. So when you instantiate a MultiBorder, you can give it a swing border to customize rendering (using lined, bevel, raised ... swing borders). In addition, MultiBorder can contain components.

The available MultiBorders are:

  • MultiBorder: a simple non-dynamic border
  • ToggleExpandBorder: a border containing a toggle button that can be used to expand/collapse the border content.
  • CheckBoxBorder: a border containing a checkBox component that can be used to expand/collapse or enable/disable the border content.

To put a border in a panel (using MultiBorderLayout) simply add the border to the MultiBorderConstraints. All the components added to the panel with a constraint containing one border will be inside this border. If a component is added with a constraints containing several borders, it will be inside all the borders.

The contrary is not true, if a component is added with a constraint containing no border, it can be inside some borders if for some reason the cell where this component resides is inside some borders. In fact, the exact rule is: a border is drawn around the minimal set of cells large enough to encompass all the cells containing a component added with this border.

In practice, it is very simple to use. For example the funny screenshot with crossing borders presented above is obtained with following code:

        JPanel testPane = new JPanel(new MultiBorderLayout());
        MultiBorderConstraints constr = new MultiBorderConstraints();
        constr.insets = new Insets(5, 5, 5, 5);
        constr.anchor = MultiBorderConstraints.CENTER;

        TitledBorder brdr1 = new TitledBorder(BorderFactory.createLineBorder(Color.green), "Green");
        brdr1.setTitleColor(Color.green);
        MultiBorder border1 = new MultiBorder(brdr1);
        constr.addBorder(border1);

        testPane.add(new JLabel("Grass"), constr.setConstraints(0, 0));
        testPane.add(new JLabel("Leaves"), constr.setConstraints(1, 0));
        testPane.add(new JLabel("Peace"), constr.setConstraints(0, 1));

        TitledBorder brdr2 = new TitledBorder(BorderFactory.createLineBorder(Color.red), "Red");
        brdr2.setTitleColor(Color.red);
        brdr2.setTitlePosition(TitledBorder.BOTTOM);
        brdr2.setTitleJustification(TitledBorder.RIGHT);
        MultiBorder border2 = new MultiBorder(brdr2);
        constr.addBorder(border2);

        testPane.add(new JLabel("Apple"), constr.setConstraints(1, 1));
        constr.removeBorder(border1);
        
        testPane.add(new JLabel("Fire"), constr.setConstraints(2, 1));
        testPane.add(new JLabel("Tomato"), constr.setConstraints(1, 2));
        testPane.add(new JLabel("Bull"), constr.setConstraints(2, 2));

You can see that it uses two MultiBorder containing regular swing titled borders (with customized colors). The key point is simply:

  • Reusing the same MultiBorderConstraints, so you have to add/remove your border once.
  • Add/remove you MultiBorder at the correct time so they appear around the correct label set. You can see that when the "Apple" label is added, the border2 is already added while the border1 is not yet removed. The "Apple" label is then the only label appearing inside the two borders.

To know

When using the MultiBorderLayout, you cannot add a border directly in your panel:

        JPanel testPane = new JPanel(new MultiBorderLayout());
        ...
        testPane.setBorder(BorderFactory.createEtchedBorder());

The border addition will either be ignored or cause an exception (depending if the layout was already done or not). This restriction came because the MultiBorderLayout is hacking the JComponent border rendering to behave correctly. So if you really want a border in your pane, you can:

  • Use a MultiBorder that contains all the screen components.
  • If you want an empty border, you can set global insets on the MultiBorderLayout with the setGlobalInsets(Insets) method.
  • Wrap your panel in another pane just to add the border

More Examples

TODO: link to code of the demo.