Java Swing : Wrangling Your Components

Laying out components in Swing is almost as frustrating as laying out components with CSS. (Almost... Not quite... But almost...) It suffers from the some of same inexplicable twitchiness as CSS ("where did those 2 extra pixels come from?!", "why isn't my label lined up with my check box same way it is with my radio button?!", and so forth). But Swing has another problem. I call it: Death By Component.

After hours of tweaking, tuning, and adjusting you finally perfect an intricate layout for your application using a combination of BorderLayout, GridLayout, and FlowLayout... Only to realize that you did so by creating 137 nested JPanel objects.

And what are you going to name all those?

JPanel mainPanel = new JPanel();  
...
JPanel contentPanel = new JPanel();  
mainPanel.add(contentPanel)  
...
JPanel centerPanel = new JPanel();  
contentPanel.add(centerPanel);  
...
JPanel outerPanel = new JPanel();  
centerPanel.add(outerPanel);  
...
JPanel innerPanel = new JPanel();  
outerPanel.add(innerPanel);  
...
JPanel basePanel = new JPanel();  
innerPanel.add(basePanel);  
...
JPanel upperPanel = new JPanel();  
basePanel.add(upperPanel);  
...
JPanel lowerPanel = new JPanel();  
upperPanel.add(lowerPanel);  
...

Or should the upperPanel be inside the innerPanel? That does not sound right. Maybe I should rename innerPanel to something else like outerPanel... Oh wait that is taken already. Drats!

Yeah... There has to be a better way.

There are a couple ways to address this problem (and feel free to sound off in the comments with your ideas!) but my current preferred method is to use anonymous classes and initializer blocks.

For example:

JPanel rootPanel = new JPanel();  
rootPanel.setLayout(new BorderLayout());

JPanel northPanel = new JPanel();  
northPanel.setBorder(new BevelBorder(BevelBorder.RAISED));  
JPanel topMenu = new JPanel();  
topPanel.setLayout(new GridLayout(1, 6);  
...
northPanel.add(topMenu);  
rootPanel.add(northPanel, BorderLayout.NORTH);

JPanel centerPanel = new JPanel();  
centerPanel.setLayout(new BoxLayout(centerPanel, BoxLayout.Y_AXIS));  
JPanel panel1 = new JPanel();  
...
centerPanel.add(panel1);  
JPanel panel2 = new JPanel();  
...
rootPanel.add(panel2);  
JPanel panel3 = new JPanel();  
...
centerPanel.add(panel3);  
JPanel panel4 = new JPanel();  
...
centerPanel.add(panel4);  
rootPanel.add(centerPanel, BorderLayout.CENTER);

JPanel westPanel = new JPanel();  
JPanel sideMenu = new JPanel();  
sidePanel.setLayout(new GridLayout(12, 1);  
...
westPanel.add(sideMenu);  
rootPanel.add(westPanel, BorderLayout.WEST);

add(rootPanel);

(A side menu, a top nav bar, and some center content.)

It is really not visually obvious what is happening here. Other than the white space seperating the blocks, you have to carefully read the code to see the relationships. Certainly as the complexity scales (imagine a hundred components instead of just a dozen), it would require significant analysis to understand the full network of relationships in this layout.

Having read (and written!) a lot of code like this, I found myself wanting something better. Particularly I wanted two things: (1) eliminate the need to create and name many throw away panels and (2) syntactically enforce the nested relationships.

To illustrate (2), very astute readers might notice that panel2 in the above example is actually added (incorrectly) to the rootPanel instead of to the centerPanel. Even though the code compiles fine, it is wrong semantically.

So here is my solution. It uses two concepts: (1) anonymous inner classes and (2) initializer blocks.

The basic construct:

add(new JPanel() {{  
    setLayout(new BorderLayout());

    add(new JPanel() {{
        ...
    }});
    ...
}});

The double pair of brackets is confusing, so let's break that down:

add (              // (1) Nested inside a method call, so no  
                   //     need to create a throw away variable.
  new JPanel() {   // (2) Start an anonymous inner class.
    {              // (3) This is an initializer block. It is
                   //     executed once per object  
                   //     instantiation before the
                   //     constructor.
      setLayout(new BorderLayout());
      add(new JPanel() {{ // (4) Rinse and repeat.
           ...
      }});
    }
  }
);

The anonymous inner class allows you to nest components indefinitely within each other, even within method calls. The initializer block allows you to call methods directly on the new object without needing to reference a variable.

Rewriting the example from above, you will see the elegance of putting the two together:

add(new JPanel() {{  
    setLayout(new BorderLayout());
    add(new JPanel() {{
        setBorder(new BevelBorder(BevelBorder.RAISED));
        add(new JPanel() {{
            setLayout(new GridLayout(1, 6));
            ...
        }});
    }}, BorderLayout.NORTH);  

add(new JPanel() {{  
        setLayout(new BoxLayout(centerPanel, BoxLayout.Y_AXIS));
        add(new JPanel() {{
           ...
        }});        
        add(new JPanel() {{
           ...
        }});        
        add(new JPanel() {{
           ...
        }});        
        add(new JPanel() {{
           ...
        }});        
    }}, BorderLayout.CENTER);

add(new JPanel() {{  
        add(new JPanel() {{
            setLayout(new GridLayout(12, 1));

            ...
        }});        
    }}, BorderLayout.WEST);        
}});

It may be a lot of brackets, but the above notation has the benefits we were looking for: (1) not a single throw away variable anywhere to be seen and (2) nesting is enforced by the compiler. You can rearrange components by simply moving them into a different initializer block. No need to try to track down which component you need to call add() on. Just move the code into the right component and you are done!

Questions? Comments? Email me at: [email protected]