![]() |
|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|
![]() |
![]() |
SummaryBy David Bismut, Krishnakumar Pooloth
In this article, David Bismut and Krishnakumar Pooloth presentCloseAndMaxTabbedPane
, an enhanced tabbed pane with Close and Maximize buttons and a look and feel similar to Eclipse 2.1. The authors discuss the component's design and implementation aspects. They also explain the design of theBasicTabbedPaneUI
class, which is an important element in the implementation. (2,000 words; September 6, 2004)
rofessional Java Swing-based applications implement custom Swing
components to enhance their functionality and usability. For example, IDEs like
Eclipse and NetBeans use an enhanced tabbed pane as a container for source code
editors. This tabbed pane has a Close icon on each tab that lets you easily
close individual tabs.
In this article, we present the implementation of
CloseAndMaxTabbedPane
, an enhanced tabbed pane with Close and
Maximize buttons. The component's look and feel is similar to Eclipse 2.1. You
can also selectively enable/disable the Close or Maximize buttons as necessary.
Figure 1 illustrates the screenshot of CloseAndMaxTabbedPane
.
Figure 1. CloseAndMaxTabbedPane in action. Click on thumbnail to view
full-size image. |
Implement CloseAndMaxTabbedPane
The
UML diagram in Figure 2 shows the implementation's most critical classes and how
they relate to standard Java Swing classes.
Figure 2. High-level UML diagram of CloseAndMaxTabbedPane
|
Descriptions of the component's critical classes follow:
CloseTabbedPaneUI
(extends
javax.swing.BasicTabbedPaneUI
): This class is the new component's
UI delegate object and is responsible for the tabbed pane's graphical aspects
(including painting the Close and Maximize buttons). This class also fires
events to the tabbed pane itself based on user actions and tries to be
friendly with the application's current look and feel.
CloseTabbedPaneEnhancedUI
(extends
CloseTabbedPaneUI
): This class implements a different look and
feel similar to the tabbed pane found in Eclipse 2.1. It overrides a set of
paint methods to implement the enhanced look and feel.
CloseAndMaxTabbedPane
(extends
javax.swing.JTabbedPane
): This class extends the Java standard
JTabbedPane
and uses the CloseTabbedPaneUI
. It also
implements methods to add specific listeners (to Close and Maximize buttons)
to the tabbed pane and interact with the UI.Add buttons to the tabbed pane
It would be easy to add
buttons if each tab were a separate Swing component. Ideally, then you would add
a Close button by simply doing the following (forgetting layout aspects for the
moment):
JButton closeButon = new JButton(closeButtonIcon);
tab.add(closeButton);
And then adding an action listener by:
closeButton.addActionListener(someActionListener);
Unfortunately adding buttons is not that easy. Each tab in a tabbed pane is
not a Swing component. Each tab's header (border, title, icon) is painted by the
BasicTabbedPaneUI
class. To paint our own version of the tabbed
pane (i.e., to add the little buttons in the corner of each tab), we need to
extend this class and override the paint methods to actually draw the buttons.
Please note that the buttons are just painted graphics and not standard Java
JButton
components.
Interact with the pseudo buttons
Because there are no
real buttons, we can't add action listeners to them. The only way to interact
with these painted buttons is to add a mouse listener to the tabbed pane itself
(or to one of its components). This mouse listener can then track mouse clicks
or movements in the zone where the buttons are painted and fire specific events
to the tabbed pane.
Create the extended UI class, CloseTabbedPaneUI
Before
going further, we need some basic understanding of how the
BasicTabbedPaneUI
works. We focus only on the scroll-tab layout
policy.
When you create a regular JTabbedPane
, the default UI is an
instance of the BasicTabbedPaneUI
class. This instance has a
reference to the tabbed pane itself, and it also sets the appropriate layout
manager for the chosen tab layout policy. In the case of the scroll-tab layout,
the layout manager creates a set of objects responsible for catching mouse
actions from the user. We'll cover this in greater detail when we look at the
implementation of mouse handlers. For now, just understand that the tabbed pane
has some extra components when used with the scroll-tab layout policy.
So to implement our enhanced tabbed pane, we need to create a
CloseTabPaneUI
class that extends the
BasicTabbedPaneUI
class. We need to override the relevant paint
methods from the base class and also add our mouse listeners.
Paint the pseudo buttons
To paint a tab, the UI uses the
following method:
protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects, int
tabIndex, Rectangle iconRect, Rectangle textRect)
where:
g
is the tabbed pane's graphic context
tabPlacement
is the tabs' positions in the tabbed pane (top,
left, right, or bottom)
rects
is an array of rectangles that bounds the painting area
reserved for each tab
tabIndex
is the index of a tab being painted
iconRect
is the rectangle that bounds the painting area
reserved for the tab icon
textRect
is the rectangle that bounds the painting area
reserved for the tab textThe layout of these rectangles is performed by the function
layoutLabel()
(we discuss more about this a bit later), which calls
a SwingUtilities
function, layoutCompoundLabel
.
Basically, the BasicTabbedPaneUI
paints something similar to the
following:
|
And we want to change it to something like this:
|
To achieve this, we must resize the black rectangle, which is part of the
rects
array and bounds the tab area, and then add the two buttons
on the right.
The resizing is straightforward, as each tab's width is provided by the
method calculateTabWidth()
. We just override this method using the
following model:
protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics
metrics) {
return super.calculateTabWidth(tabPlacement, tabIndex, metrics) +
someExtraSpace;
}
Now we can override the paintTab()
method itself and add our
buttons:
protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects, int
tabIndex, Rectangle iconRect, Rectangle
textRect){
super.paintTab(...);
Rectangle tabRect =
rects[tabIndex]; //the black rectangle
int selectedIndex =
tabPane.getSelectedIndex();
boolean isSelected = selectedIndex ==
tabIndex;
boolean isOver = overTabIndex == tabIndex;
/*
* isOver
returns true if the mouse is over the tab which is currently
* painted.
*
overTabIndex returns the index of the tab the mouse is currently on.
*
overTabIndex is an instance of the class, and is modified
* by the mouse
listeners.
*/
// Like in Eclipse 2.1, the icons are painted only if
the tab is
// selected or if the mouse is over the tab.
if (isOver ||
isSelected) {
// Calculate the coordinates where the buttons should
be.
int dx = tabRect.x + tabRect.width - BUTTONSIZE - WIDTHDELTA;
int dy =
(tabRect.y + tabRect.height) / 2 - 6;
//Paint the Close and Max
buttons
paintCloseIcon(g, dx, dy, isOver);
paintMaxIcon(g, dx, dy, isOver);
}
}
Add the mouse listeners
New mouse listeners for
CloseTabPaneUI
are required to handle user clicks on the Close or
Maximize buttons and provide a rollover effect when the mouse hovers over the
buttons. We need a MouseListener
for tracking user clicks and a
MouseMotionListener
for supporting rollover effects.
To understand this, we need to dig a bit deeper into the scroll-tab layout
manager. The scroll-tab layout manager relies on an instance of class
ScrollableTabSupport
, tabScroller
, which implements
the tabbed pane's bar itself. It works similarly to a scroll pane.
ScrollableTabSupport
is an inner private class of the
BasicTabbedPaneUI
class. This class is composed of two main
objects:
tabPanel
(not to be confused with the tabPane
instance, which references the tabbed pane itself): An instance of
ScrollableTabPanel
(extends from JPanel
) that
contains the tab painting area and the arrow buttons to scroll the tabs.
viewport
: An instance of ScrollableTabViewport
(extends from JViewport
) and allows the display of only a certain
number of tabs. For more information on how viewport
s work,
please refer to Sun's Java documentation.Both of the above classes are private
inner classes as well.
Also, the tabScroller
instance is a private
member of
BasicTabbedPaneUI
. Hence, it is not possible to access it from our
extended CloseTabPaneUI
class.
To circumvent this problem, we provided our own implementation of
tabScroller
for CloseTabPaneUI
. Our implementation
copied a lot of code from the base class version and features some additional
logic that handles mouse events on the buttons. Please refer to the source code,
which can be downloaded from Resources,
for more details.
The mouse listener BasicTabbedPaneUI
implements a mouse listener, which processes mouse clicks on the tabs to change
the selected tab. CloseTabPaneUI
extends this mouse listener to
provide additional functionality, as shown in the code below. We focus on the
mouseReleased()
method and the Close button to understand the
logic. For the other methods and further details, please refer to the source
code.
class MyMouseHandler extends MouseHandler {
public
MyMouseHandler()
{
super();
}
public void mouseReleased(MouseEvent e)
{
/*
* This function updates the
overTabIndex variable
* depending on the
mouse position.
*/
updateOverTab(e.getX(),
e.getY());
if (overTabIndex == -1)
return;
if (closeIndexStatus ==
PRESSED) {
closeIndexStatus
= OVER;
tabScroller.tabPanel.repaint();
((CloseAndMaxTabbedPane)tabPane).fireCloseTabEvent(e,
overTabIndex);
return;
}
}
}
closeIndexStatus
is a member of the class
CloseTabbedPaneUI
, which represents the Close button's status of
the tab the mouse is currently over. The possible values of
closeIndexStatus
are INACTIVE
, OVER
, or
PRESSED
. Be aware that the closeIndexStatus
does not
know to which tab it refers to, so closeIndexStatus
must always be
used with overTabIndex
.
When the mouse is released over a tab, the method checks whether the user was
previously pressing the Close button. If the status is true, then the status of
the Close button is now OVER
, as the mouse has been released. We
then need to repaint the tab bar and notify the tabPane
(i.e., the
tabbed pane the UI is responsible for) by firing an event. Notice that the
fireCloseTabEvent()
method is specific to the
CloseAndMaxTabbedPane
, so we must cast the tabPane
object beforehand appropriately.
The mouse motion listener
As opposed to the mouse
listener, no standard mouse motion listener for BasicTabbedPaneUI
is available. So we need to implement one from scratch:
class MyMouseMotionListener implements MouseMotionListener
{
public void mouseMoved(MouseEvent e)
{
mousePressed =
false;
setTabIcons(e.getX(),
e.getY());
}
public void
mouseDragged(MouseEvent e) {
mousePressed
= true;
setTabIcons(e.getX(),
e.getY());
}
}
mousePressed
is a boolean
member of the
CloseTabbedPaneUI
, representing the mouse button's status, whether
it is pressed or not. Some of the mouse listener's other methods use this
variable.
The setTabIcons()
method sets the status of both the Close and
Maximize buttons according to the mouse position. For example, when the mouse is
inside the Close button's zone, it sets the closeStatusIndex
to
OVER
or PRESSED
if the mouse is pressed.
Now, the question is which component should we add the listeners to? That is,
which component tracks these mouse events? You might say the
tabPane
object, which is the instance of the tabbed pane itself.
Well, that would be true if you use the wrap-tab layout policy. In the case of
the scroll-tab layout policy, the component that matters is
tabScroller.tabPanel
.
To use the newly defined listener, you need to override the
createMouseListener()
method so it can return your own mouse
listener:
protected MouseListener createMouseListener() {
return new
MyMouseHandler();
}
Then override the installListeners()
method to add the mouse
motion listener to the tabScroller.tabPanel
object:
protected void installListeners() {
//... Some code
here
if ((motionListener = new MyMouseMotionListener()) != null)
{
tabScroller.tabPanel.addMouseMotionListener(motionListener)
}
}
And that's all you need to know about the CloseTabbedPaneUI
class.
Create the extended JTabbedPane class, CloseAndMaxTabbedPane
Now that we have the UI, we need an enhanced tabbed pane that can
handle the events fired by the UI, and the CloseAndMaxTabbedPane
class is responsible for that task.
This class is fairly simple. Here is the constructor (simplified version):
public CloseAndMaxTabbedPane() {
super.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
paneUI
= new CloseTabPaneUI();
super.setUI(paneUI);
}
Methods to add listeners to a specific type of event follow. This one adds a listener to the close event:
public void addCloseListener(CloseListener l) {
listenerList.add(CloseListener.class, l);
}
Next are methods to fire a specific type of event to the listeners:
public void fireCloseTabEvent(MouseEvent e, int overTabIndex)
{
this.overTabIndex = overTabIndex;
EventListener cListeners[] = getListeners(CloseListener.class);
for (int i = 0; i < cListeners.length; i++)
{
((CloseListener)
cListeners[i]).closeOperation(e);
}
}
Use CloseAndMaxTabbedPane
First
create a CloseAndMaxTabbedPane
:
/*
* The true argument in the constructor means that the tabbed pane
*
is going to use an advanced interface. See the
* CloseTabbedPaneEnhancedUI
class for more details.
*/
CloseAndMaxTabbedPane tabbedPane = new
CloseAndMaxTabbedPane(true)
Then add a close listener to the pane to close a tab when the user clicks on its Close button:
tabbedPane.addCloseListener(new CloseListener() {
public void
closeOperation(MouseEvent e)
{
tabbedPane.remove(tabbedPane.getOverTabIndex());
}
});
A complete example application using the component is provided along with the source code in Resources.
Limitations
Currently, the
CloseAndMaxTabbedPane
implementation has the following limitations:
TOP
tab placement which is most
commonly used. It does not support the other standard options
LEFT
, RIGHT
, or BOTTOM
.Note on the design of BasicTabbedPaneUI class
While developing this component, we faced certain
implementation issues because of the way the BasicTabbedPaneUI
class is defined. An instance of LayoutManager
is responsible for
managing the JTabbedPane
. The BasicTabbedPaneUI
has a
default layout, the wrap-tab layout, which is implemented by the class
TabbedPaneLayout
. This class is a protected
class,
nested into the BasicTabbedPaneUI
class, which means it could be
extended.
The scroll-tab layout is implemented by the class
TabbedPaneScrollLayout
, which is also nested in the
BasicTabbedPaneUI
class, and extends the class
TabbedPaneLayout
. Things are fine until this point. The difficulty
is that this TabbedPaneScrollLayout
class is private
,
which means extending this class or even modifying any object belonging to it is
impossible. The only solution is to copy and paste the
TabbedPaneScrollLayout
directly from the
BasicTabbedPaneUI
class and modify it to suit your requirements.
Now, there could be important reasons why many of these inner classes are
private
. The class's design could be revamped, for example. The
important fact is that when you try to clone a private class or method from this
class into your code, you would see that this class or method makes calls to
other private classes or methods, which you must clone as well! Ultimately, to
make things work, you'll have to clone a lot of code you don't even care about.
Conclusion
The
CloseAndMaxTabbedPane
class overcomes several limitations of the
standard Java JTabbedPane
by adding the facility of closing and
maximizing individual tabs. It also lets users selectively enable/disable any of
these new buttons. Moreover, it provides a more professional look and feel
similar to Eclipse 2.1. Not all user interfaces require these additional
features of course, but those developers currently using the
JTabbedPane
class or planning a Java-based multiple-document
interface environment should consider its usage.
We have used the CloseAndMaxTabbedPane
class successfully in
many of our own applications. We welcome any feedback regarding your own
experiences with the class, either positive or negative, as well as comments
pertaining to the design and layout.
About the author
David Bismut is a third-year student at
the Ecole des Mines in Nantes (EMN), a French
engineering school. He specializes in information management technologies. He
was part of InStep, Infosys's
global internship program in 2004. You can contact him at
david.bismut@gmail.com.
Krishnakumar Pooloth is a technical architect with Infosys Technologies. His areas of expertise
include object design, component technology, Java, and expert systems. He holds
a bachelor's degree in electronics and communication from Calicut University,
India. You can contact him at krishnakumarp@infosys.com.
![]() |
![]() |
|
![]() |
Copyright © 2004 JavaWorld.com, an IDG company |
![]() |
![]() |
![]() |