Subpages: 1. JTabbedPane
2. Dynamically changeable tabbed pane
3. Customized JTabbedPane and TabbedPaneUI delegate
6.3 Customized JTabbedPane and TabbedPaneUI delegate
Although we intend to save most of our discussion of customizing UI delegates for chapter 21, building fancy-looking tabs is too tempting to pass up, and this example may satisfy your UI customization appetite for now. You can use the techniques shown here to implement custom UI delegates for almost any Swing component. However, there will be major differences from component to component, and this will almost always involve referencing the Swing source code.
First we will build a customized JTabbedPane with two properties: a background image used for each tab, and a background image used for the tabbed pane itself. We will then build a subclass of BasicTabbedPaneUI for use with our customized JTabbedPane, and design it so that it paints the tab background image on each tab. This will require modification of its paint() method. Our custom delegate will also be responsible for painting the tabbed pane background. As we learned in chapter 2 in our discussion of painting, a UI delegate's update() method is used for filling the background of opaque components and then it should pass control to paint(). Both images need to be accessible from our customized JTabbedPane using set and get accessors. (In keeping with the concept of UI delegates not being bound to specific component instances, it would not be a good idea to assign these images as UI delegate properties--unless we were building an image specific L&F.)
BasicTabbedPaneUI is the second largest (in terms of source code) and complex UI delegate. It contains, among other things, its own custom layout manager, TabbedPaneLayout, and a long rendering routine spread out over several different methods. As complex as it is, understanding its inner workings is not required to build on top of it. Since we are concerned only with how it does its rendering, we can narrow our focus considerably. To start, its paint() method calls the paintTab() method which is responsible for painting each tab. This method calls several other methods to perform various aspects of this process (see BasicTabbedPaneUI.java). Briefly, and in order, these methods are:
paintTabBackground()
paintTabBorder()
layoutLabel()
paintText()
paintIcon()
paintFocusIndicator()
By overriding any combination of these methods we can control the tab rendering process however we like. Our customized TabbedPaneUI, which we'll call ImageTabbedPaneUI, overrides the paintTabBackground() method to construct tabs with background images.

Figure 6.2 TabbedPaneDemo with custom tab rendering, LEFT layout
<<file figure6-2.gif>>
![]()
Figure 6.3 TabbedPaneDemo with custom tab rendering, BOTTOM layout
<<file figure6-3.gif>>
The Code: TabbedPaneDemo.java
see \Chapter6\2
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.plaf.TabbedPaneUI;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
public class TabbedPaneDemo extends JApplet
implements ActionListener
{
// Unchanged code from section 6.2
public void init() {
m_loading = new JLabel("Initializing applet...",
SwingConstants.CENTER);
getContentPane().add(m_loading);
Thread initialize = new Thread() {
public void run() {
// Unchanged code from section 6.2
m_tabbedPane = new ImageTabbedPane(
new ImageIcon("bloo.gif"),
new ImageIcon("bubbles.jpg"));
// Unchanged code from section 6.2
}
};
initialize.start();
}
public void createTab() {
// Unchanged code from section 6.2
label.setBackground(Color.white);
m_tabbedPane.addTab("Tab #" + m_tabbedPane.getTabCount(),
m_tabimage, label);
m_tabbedPane.setForegroundAt(m_tabbedPane.getTabCount()-1,
Color.white);
m_tabbedPane.setSelectedIndex(m_tabbedPane.getTabCount()-1);
setStatus(m_tabbedPane.getSelectedIndex());
}
// Unchanged code from section 6.2
}
class ImageTabbedPane extends JTabbedPane
{
// Display properties
private Image m_tabBackground;
private Image m_paneBackground;
public ImageTabbedPane(ImageIcon tabBackground,
ImageIcon paneBackground) {
m_tabBackground = tabBackground.getImage();
m_paneBackground = paneBackground.getImage();
setUI((ImageTabbedPaneUI) ImageTabbedPaneUI.createUI(this));
}
public void setTabBackground(Image i) {
m_tabBackground = i;
repaint();
}
public void setPaneBackground(Image i) {
m_paneBackground = i;
repaint();
}
public Image getTabBackground() {
return m_tabBackground;
}
public Image getPaneBackground() {
return m_paneBackground;
}
}
class ImageTabbedPaneUI extends BasicTabbedPaneUI
{
private Image m_image;
public static ComponentUI createUI(JComponent c) {
return new ImageTabbedPaneUI();
}
public void update(Graphics g, JComponent c) {
if (c instanceof ImageTabbedPane) {
Image paneImage = ((ImageTabbedPane) c).getPaneBackground();
int w = c.getWidth();
int h = c.getHeight();
int iw = paneImage.getWidth(tabPane);
int ih = paneImage.getHeight(tabPane);
if (iw > 0 && ih > 0) {
for (int j=0; j < h; j += ih) {
for (int i=0; i < w; i += iw) {
g.drawImage(paneImage,i,j, tabPane);
}
}
}
}
paint(g,c);
}
public void paint(Graphics g, JComponent c) {
if (c instanceof ImageTabbedPane)
m_image = ((ImageTabbedPane) c).getTabBackground();
super.paint(g,c);
}
protected void paintTabBackground(Graphics g, int tabPlacement,
int tabIndex, int x, int y, int w, int h, boolean isSelected )
{
Color tp = tabPane.getBackgroundAt(tabIndex);
switch(tabPlacement) {
case LEFT:
g.drawImage(m_image, x+1, y+1, (w-2)+(x+1), (y+1)+(h-3),
0, 0, w, h, tp, tabPane);
break;
case RIGHT:
g.drawImage(m_image, x, y+1, (w-2)+(x), (y+1)+(h-3),
0, 0, w, h, tp, tabPane);
break;
case BOTTOM:
g.drawImage(m_image, x+1, y, (w-3)+(x+1), (y)+(h-1),
0, 0, w, h, tp, tabPane);
break;
case TOP:
g.drawImage(m_image, x+1, y+1, (w-3)+(x+1), (y+1)+(h-1),
0, 0, w, h, tp, tabPane);
}
}
}
Understanding the Code
Class TabbedPaneDemo
We have replaced the JTabbedPane instance with an instance of our custom ImageTabbedPane class. This class's constructor takes two ImageIcons as parameters. The first represents the tab background and the second represents the pane background:
m_tabbedPane = new ImageTabbedPane(
new ImageIcon("bloo.gif"),
new ImageIcon("bubbles.jpg"));
We've also modified the createTab() method to make the tab foreground text white because the tab background image we are using is dark.
Class ImageTabbedPane
This JTabbedPane subclass is responsible for keeping two Image variables that represent our custom tab background and pane background properties. The constructor takes two ImageIcons as parameters, extracts the Images, and assigns them to these variables. It calls setUI() to enforce the use of our custom ImageTabbedPaneUI delegate. This class also defines set() and get() accessors for both Image properties.
Class ImageTabbedPaneUI
This class extends BasicTabbedPaneUI and maintains an Image variable for use in painting tab backgrounds. The creatUI() method is overridden to return an instance of itself (remember that this method was used in the ImageTabbedPane constructor):
public static ComponentUI createUI(JComponent c) {
return new ImageTabbedPaneUI();
}
The update() method, as we discussed above, is responsible for filling the pane background and then calling paint(). So we override it to perform a tiling of the pane background image. First we grab a reference to that image check its dimensions. If any are found to be 0 or less we do not go through with painting procedure. Otherwise we tile a region of the pane equal in size to the current size of the ImageTabbedPane itself. Note that in order to get the width and height of an Image we use the following method calls:
int iw = paneImage.getWidth(tabPane);
int ih = paneImage.getHeight(tabPane);
(The tabPane reference is protected in BasicTabbedPaneUI and we use it for the ImageObsever here--it is a reference to the JTabbedPane component being rendered.) Once the tiling is done we call paint().
The paint() method simply assigns this class's tab background Image variable to that of the ImageTabbedPane being painted. It then calls its superclass's paint() method which we do not concern ourselves with--let alone fully understand! What we do know is that the superclass's paint() method calls its paintTab() method, which in turn calls its paintTabBackground() method, which is actually responsible for filling the background of each tab. So we overrode this method to use our tab background Image instead. This method specifies four cases based on which layout mode the JTabbedPane is in: LEFT, RIGHT, BOTTOM, TOP. These cases were obtained directly from the BasicTabbedPaneUI source code and we did not modify their semantics at all. What we did modify is what each case does.
We use the drawImage() method of the awt.Graphics class is used to fill a rectangular area defined by the first four int parameters (which represent two points). The second four int parameters specify an area of the Image (passed in as the first parameter) that will be scaled and mapped onto the above rectangular area. The last parameter represents an ImageObserver instance.
Note: A more professional implementation would tile the images used in painting the tab background--this implementation assumes that a large enough image will be used to fill a tab's background of any size.
Running the Code:
Figures 6.2 and 6.3 show TabbedPaneDemo in action. To deploy this applet you can use the same HTML file that was used in the previous example. Try out all different tab positions.
Note that the images used are only used within the paint() and update() methods of our UI delegate. In this way we keep with Swing's UI delegate / component separation. One instance of ImageTabbedPaneUI could be assigned to multiple ImageTabbedPane instances using different Images, and there would be no conflicts. Also note that the images are loaded only once (when an instance of ImageTabbedPaneUI is created) so the resource overhead is minimal.
UI Guideline : Custom Tabs Look & Feel
After reading Chapter 21 on Custom Look and Feel , you may like to reconsider customising the Tabbed Pane styles. There are many possibilities which are worthy of consideration. For example the OS2 Warp style of scrolling tabs. This is particularly good where you cannot afford to give away screen space for an additional row or column of tabs. Another possibility is to fix the size of a tab. Currently tabs size of the width of the label which is largely controlled by the length of text and font selected. This lends greater graphical weight to tabs with long labels. The size of the visual target is greater and therefore tabs with long labels are easier to select than tabs with short labels. You may like to consider a tab which fixes a standard size, thus equalising the size of the target and avoiding biasing the usability toward the longer labelled tabs. Further possibilities are tabs with colour and tabs with icons to indicate useful information such as "updated" or "new" or "mandatory input required".


RSS feed Java FAQ News