Sometimes in Java, One Layout Manager Is Not Enough

Most often when developing Java Swing applications, we need to use several layout managers in several nested panels. This is usually not a problem and is considered the normal practice for all UI development in almost all languages known to man. However, most often for each panel in the UI only one layout manager is needed to achieve the desired effect, but there comes a time when you need to use multiple layout managers for the same container depending on the number components in the container.

One such example would be when creating a Centered Grid like layout.  Most often, GridLayout or GridBagLayout may suffice if the number of components are fixed but if the number of components keep changing, the layout may not be as desired. I faced such a similar problem this afternoon, and here is the solution I came up with.

The UI I wanted to achieve was something similar to Opera’s speed dial, but with variable number of dials. Basically,

  1. you start off with one component and that should be centered in the panel
  2. add another component, and they should both be centered
  3. add a third and all three should be centered on one row
  4. if a fourth component is added, then you should have a 3 x 2 matrix, with three items in the first row and one in the second
  5. the 3 x 2 matrix should be maintained for the up to six components
  6. for more than 6 components, the matrix should be 4 x 3, so we can take up to 12 components, which will be maximum

This requirement initially looked tricky, but the solution was as usual a combination of layout managers using just two JPanels. Basically, an outer container for centering the internal contents, and an internal container for creating the matrix as required.

Which Layout Manager To Use

The question then arose, which layout manager gives the desired results? After minutes of experimentation, I finally realized that a GridBagLayout gives me the centered content appearance I need, but it was sometimes inconsistent. So I chose to use GroupLayout, via the netbeans designer. So that was applied the outer container.

Next I tried to find one layout manager that will effectively fulfill the first requirement. The options were FlowLayout and GridLayout. However, eventhough FlowLayout tends to vertically align its contents to the top, it was adequate in this case cause the GroupLayout vertically centered the FlowLayout content, and this fulfilled requirements 1 – 3.

Next, for requirement 4, GridLayout was chosen again, but this time, it was set to be a n x 3 matrix, where n is any number of rows. This allows the GridLayout to grows as expected and also lay out its components horizontally first before vertically. This automatically also fulfilled requirement 5.

Finally, when the components become greater than 6, a new GridLayout is created which is n x 4, effectively lining up the contents are desired. New components can be further added until the maximum of 12 components is reached, and further addition is prohibited.

Here is the example code for the process.

JPanel container = new JPanel();
container.setName("container"); // NOI18N
container.setOpaque(false);

JPanel content = new JPanel();
content.setBorder(javax.swing.BorderFactory.createEmptyBorder(50, 50, 50, 50));
content.setName("content"); // NOI18N
content.setOpaque(false);
content.setLayout(new java.awt.GridLayout(0, 3));

javax.swing.GroupLayout containerLayout = new javax.swing.GroupLayout(container);
container.setLayout(containerLayout);
containerLayout.setHorizontalGroup(
    containerLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
    .addGroup(containerLayout.createSequentialGroup()
        .addContainerGap(346, Short.MAX_VALUE)
        .addComponent(content, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
        .addContainerGap(347, Short.MAX_VALUE))
);
containerLayout.setVerticalGroup(
    containerLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
    .addGroup(containerLayout.createSequentialGroup()
        .addContainerGap(223, Short.MAX_VALUE)
        .addComponent(content, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
        .addContainerGap(224, Short.MAX_VALUE))
);

And then whenever a new component is added, the following code is run

getContent().removeAll();

int gridSize = organisations.size();

switch( gridSize ) {
    case 1:
    case 2:
    case 3:
        getContent().setLayout( new FlowLayout(FlowLayout.CENTER) );
        break;
    case 4:
    case 5:
    case 6:
        getContent().setLayout( new GridLayout(0,3) );
        break;
    case 7:
    case 8:
        getContent().setLayout( new GridLayout(0,4) );
        break;
    default:
        getContent().setLayout( new GridLayout(0, 4) );
}

for (Organisation org : organisations) {
    getContent().add(createOrgSelectionComponent(org));
}

getContent().validate();
getContent().repaint();

And here is a screen shot of the final layouts.

So if you are ever in a fix like this, don’t hesistate to consider using more than one layout for the problem. Happy coding!

About these ads

11 thoughts on “Sometimes in Java, One Layout Manager Is Not Enough

  1. Pingback: My Latest App Takes Wings « ICED IN CODE

  2. Alex

    Hi ,
    This is quit a nice article about using several Layouts and since i am newbie i can say that for mit was well written.

    Thanks
    Alex

    Reply
  3. Bambang

    Hi,
    Do you have a facebook account?
    I like your articles. i am a java programmer and i come from Indonesia.
    nice to meet you

    TQ
    Bamzz

    Reply
  4. Randolf Richardson

    Opera has a lot of innovative features in it, and the speed dial is one of my favourites. Your solution for doing this in Java is very simple without sacrificing flexibility, which also makes it a practical solution. Well done.

    Reply
    1. Francis Adu-Gyamfi Post author

      Thanks for the encouragement. And also Opera is my favourite and default browser of choice, so I will certainly mimic them when possible. lol!

      Reply
  5. Olivier

    That’s a perfect case where a custom layout manager is the most appropriate. Especially since the components your are laying out seems to be fixe-sized, which makes the computations easy to do.

    Reply
    1. Olivier

      I think the following layout has the behavior you are looking for :

      public class SuperLayout implements LayoutManager {
      
          int COMP_W = 100;
          int COMP_H = 70;
      
          public void addLayoutComponent(String name, Component comp) {
              
          }
      
          public void removeLayoutComponent(Component comp) {
              
          }
      
          public Dimension preferredLayoutSize(Container parent) {
              return computeDim(parent.getComponentCount());
          }
      
          public Dimension minimumLayoutSize(Container parent) {
              return computeDim(parent.getComponentCount());
              
          }
      
          public void layoutContainer(Container parent) {
              int nbCol = computeNbCol(parent.getComponentCount());
              layoutGrid(parent, nbCol);
          }
      
          private void layoutGrid(Container parent, int nbCol) {
              int parentWidth = parent.getWidth();
              int parentHeight = parent.getHeight();
              int count = parent.getComponentCount();
              for (int i = 0; i < count; i++) {
                  Component c = parent.getComponent(i);
                  int x = i % nbCol;
                  int y = i / nbCol;
                  Rectangle r = computeBounds(x, y);
                  int compsW = computeNbCol(count) * COMP_W;
                  int compsH = computeNbRow(count) * COMP_H;
                  r.translate(parentWidth/2 - compsW/2, parentHeight/2 - compsH/2);
                  c.setBounds(r);
              }
          }
      
          private Rectangle computeBounds(int x, int y) {
              Rectangle res = new Rectangle();
              res.x = x * COMP_W;
              res.y = y * COMP_H;
              res.width = COMP_W;
              res.height = COMP_H;
              return res;
          }
      
          private Dimension computeDim(int nbComp) {
              Dimension d = new Dimension();
              d.width = computeNbCol(nbComp) * COMP_W;
              d.height = computeNbRow(nbComp) * COMP_H;
              return d;
          }
      
          private int computeNbRow(int nbComp) {
              return (nbComp-1) / computeNbCol(nbComp) + 1;
          }
      
          private int computeNbCol(int nbComp) {
              if (nbComp <= 3) {
                  return nbComp;
              } else if (nbComp <= 6) {
                  return 3;
              } else {
                  return 4;
              }
          }
      }
      
      Reply
      1. Francis Adu-Gyamfi Post author

        Awesome, will do well to try it out. :) I’ve never been good at building my own layouts, but I guess I will start now.

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