CardPanel - An Alternative to CardLayout
by Hans Muller
Last week I spent a few days writing an application GUI that required
support for displaying a stack of panels, all the same size, where
only one panel was visible at a time. When the number of panels
is small and fixed, a JTabbedPane
is often used. In my application the number of panels varied dynamically
between one and twenty-five, which made it unsuitable for a tabbed
pane. In addition, the tabbed pane displays the name of each panel
to the user, and this wasn't desirable for my GUI.
I decided to use a container with CardLayout
instead. CardLayout is an AWT layout manager that arranges
an arbitrary number of optionally named components in a stack that's
as big as the largest component. It provides methods for restacking
a component to the top, thus making it visible, either by name or
by relative position in the stack. For example, CardLayout.next()
moves the component below the one that's currently visible to the
top, thus making it the visible component.
Although CardLayout was designed specifically for
the problem I was addressing, I wasn't entirely happy with it. I
found it inconvenient to use because, to restack, I needed to cast
the container that used CardLayout a JPanel
in my application. For example to show the card named "myCard"
in a JPanel with a CardLayout you would
write:
((CardLayout)(myJPanel.getLayout())).show(myJPanel, "myCard");
Although it's easy to work around this syntactic inconvenience,
a more subtle problem lurks below the surface. All of the CardLayout
display operations, such as CardLayout's show() call
validate directly to ensure that the newly visible component
or "card" is resized to fill the CardLayout
container. This is very unusual for a layout manager; even though
many of the AWT and Swing layout managers provide an API for configuring
their properties, CardLayout is the only one that calls
validate directly.
Swing components support automatic, batched validation by calling
revalidate
whenever a change is made that may require a containers size, or
the layout of its children, to change. The Swing revalidate
method is like repaint in the sense that validate
requests are compacted and efficiently handled en-masse at the end
of each event-dispatching cycle. In a Swing application a direct
call to validate is inefficient, and it forces the
application to update the bounds of invalid components piecemeal
and sometimes redundantly.
To remedy these problems I wrote a simple JPanel subclass
called CardPanel. CardPanel contains its
own private CardLayout implementation that uses revalidate
as appropriate. It also exposes CardLayout's methods
so there is no need to cast; for example, CardLayout.show(cardName)
is exposed as CardPanel.showCard(cardName). The
new class is intended to support a layout with a modest number of
cards, on the order of 100 or less. A card's name is the component
name set when the component is added to the CardPanel;
it can be retrieved with java.awt.Component.getName():
myCardPanel.add(myChild, "MyChildName");
myChild.getName() => "MyChildName"
The CardLayout class maintains a Hashtable
internally that maps component names to the components themselves.
Since most CardLayouts, and notably my application,
contain a small number of cards, the added expense of a hashtable
wasn't worth the performance gained by avoiding a linear search
when a component is looked up by name. So CardPanel takes advantage
of the otherwise underutilized Component name property
and lives with the cost of scanning through the list of children
to find one by name.
As with CardLayout, the first child added to a CardPanel
will be visible to start with, and only one child is visible at
a time. The showCard method accepts either a child's
name or the child itself:
myCardPanel.showCard("MyChildName");
myCardPanel.showCard(myChild);
The CardPanel class doesn't support the vgap and hgap
CardLayout properties since one can just add a Border
to the panel instead; see JComponent.setBorder()
. All of the other CardLayout methods are supported, with the names
lengthened a little for the sake of clarity. For example, CardLayout.next()
becomes CardPanel.showNextCard().
You can download the source code for CardPanel here.
|