Adding tab close buttons to a JTabbedPane in Java Swing
Thursday, 22 November 2012
Any Java developer who works with tabbed panes in their applications knows that the basic JTabbedPane doesn’t provide out-of-the-box functionality to close tabs. Ten years ago that was fine, but these days we expect to be able to click little buttons on our tabs and close them. Until Java 6, it just wasn’t possible to add buttons to tabs. These days, we have the ability to add our own customizations to the tab via the setTabComponentAt method.
There are several examples of how to do this on the web, but most of them show the basics of adding a button to a tab and leave it at that.
I wanted to have an easy way to add the
to any tab, while still allowing a custom tab icon and text, and I wanted the close-tab button to support rollover effects, so that it is only red when you hover over it.
Here a short sample program that demonstrates using a simple method to decorate tabs with little close buttons.
Getting the Necessary Pieces
I did an image search for a suitable icon and found a nice little 11×11 red close-tab icon at this site, from the open source Notepad++ program. I went to Icon Finder for the example Edit Page icon.
In order to easily render a grayscale version of the button icon, for rollover, I used the RGBGrayFilter utility class from JGoodies.
This example also uses the popular Plastic look and feel, so you will need to download both JGoodies Common and JGoodies Looks from their download page.
Note that the JGoodies stuff is optional—I used it for the look and for the grayscale utility, but you can easily just provide two different versions of the icon for your tab button.
The Code
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
package tab.demo; import com.jgoodies.looks.common.*; import com.jgoodies.looks.plastic.*; import com.jgoodies.looks.plastic.theme.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * A demo class for showing how to add close buttons to tabs. * * @author Tad Harrison */ public class TabDemoFrame extends javax.swing.JFrame { private static final Icon CLOSE_TAB_ICON = new ImageIcon(TabDemoFrame.class.getResource("closeTabButton.png")); private static final Icon PAGE_ICON = new ImageIcon(TabDemoFrame.class.getResource("1353549278_page-edit.png")); private int tabCount = 0; /** * Creates new TabDemoFrame */ public TabDemoFrame() { initComponents(); } /** * Adds a component to a JTabbedPane with a little "close tab" button on the * right side of the tab. * * @param tabbedPane the JTabbedPane * @param c any JComponent * @param title the title for the tab * @param icon the icon for the tab, if desired */ public static void addClosableTab(final JTabbedPane tabbedPane, final JComponent c, final String title, final Icon icon) { // Add the tab to the pane without any label tabbedPane.addTab(null, c); int pos = tabbedPane.indexOfComponent(c); // Create a FlowLayout that will space things 5px apart FlowLayout f = new FlowLayout(FlowLayout.CENTER, 5, 0); // Make a small JPanel with the layout and make it non-opaque JPanel pnlTab = new JPanel(f); pnlTab.setOpaque(false); // Add a JLabel with title and the left-side tab icon JLabel lblTitle = new JLabel(title); lblTitle.setIcon(icon); // Create a JButton for the close tab button JButton btnClose = new JButton(); btnClose.setOpaque(false); // Configure icon and rollover icon for button btnClose.setRolloverIcon(CLOSE_TAB_ICON); btnClose.setRolloverEnabled(true); btnClose.setIcon(RGBGrayFilter.getDisabledIcon(btnClose, CLOSE_TAB_ICON)); // Set border null so the button doesn't make the tab too big btnClose.setBorder(null); // Make sure the button can't get focus, otherwise it looks funny btnClose.setFocusable(false); // Put the panel together pnlTab.add(lblTitle); pnlTab.add(btnClose); // Add a thin border to keep the image below the top edge of the tab // when the tab is selected pnlTab.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0)); // Now assign the component for the tab tabbedPane.setTabComponentAt(pos, pnlTab); // Add the listener that removes the tab ActionListener listener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { // The component parameter must be declared "final" so that it can be // referenced in the anonymous listener class like this. tabbedPane.remove(c); } }; btnClose.addActionListener(listener); // Optionally bring the new tab to the front tabbedPane.setSelectedComponent(c); //------------------------------------------------------------- // Bonus: Adding a <Ctrl-W> keystroke binding to close the tab //------------------------------------------------------------- AbstractAction closeTabAction = new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { tabbedPane.remove(c); } }; // Create a keystroke KeyStroke controlW = KeyStroke.getKeyStroke("control W"); // Get the appropriate input map using the JComponent constants. // This one works well when the component is a container. InputMap inputMap = c.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); // Add the key binding for the keystroke to the action name inputMap.put(controlW, "closeTab"); // Now add a single binding for the action name to the anonymous action c.getActionMap().put("closeTab", closeTabAction); } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code"> private void initComponents() { tabbedPane = new javax.swing.JTabbedPane(); jScrollPane1 = new javax.swing.JScrollPane(); jTextArea1 = new javax.swing.JTextArea(); buttonPanel = new javax.swing.JPanel(); createTabButton = new javax.swing.JButton(); setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); setPreferredSize(new java.awt.Dimension(800, 400)); jTextArea1.setEditable(false); jTextArea1.setColumns(20); jTextArea1.setLineWrap(true); jTextArea1.setRows(5); jTextArea1.setText("This tab cannot be closed.\n\nClick on the \"Add New Tab\" button a few times.\n\nTry closing the tabs with the small buttons.\nTry closing the tabs with <Ctrl-W>."); jScrollPane1.setViewportView(jTextArea1); tabbedPane.addTab("About", jScrollPane1); getContentPane().add(tabbedPane, java.awt.BorderLayout.CENTER); createTabButton.setText("Add New Tab"); createTabButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { createTabButtonActionPerformed(evt); } }); buttonPanel.add(createTabButton); getContentPane().add(buttonPanel, java.awt.BorderLayout.SOUTH); pack(); }// </editor-fold> private void createTabButtonActionPerformed(java.awt.event.ActionEvent evt) { System.out.println("Add new tab!"); tabCount++; JScrollPane scrollPane = new JScrollPane(new JTextArea("New tab number " + tabCount)); Icon icon = PAGE_ICON; addClosableTab(tabbedPane, scrollPane, "Tab " + tabCount, icon); } /** * @param args the command line arguments */ public static void main(String args[]) { try { PlasticLookAndFeel laf = new Plastic3DLookAndFeel(); PlasticLookAndFeel.setCurrentTheme(new ExperienceBlue()); UIManager.setLookAndFeel(laf); } catch (javax.swing.UnsupportedLookAndFeelException ex) { ex.printStackTrace(); } java.awt.EventQueue.invokeLater(new Runnable() { @Override public void run() { new TabDemoFrame().setVisible(true); } }); } // Variables declaration - do not modify private javax.swing.JPanel buttonPanel; private javax.swing.JButton createTabButton; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JTextArea jTextArea1; private javax.swing.JTabbedPane tabbedPane; // End of variables declaration } |
How it Works
The static method addClosableTab will add a panel (or other component) to a JTabbedPane with a “close button” tab containing an icon and a text label.
The parameters are final, so that they can be accessed from within the anonymous classes used for listeners.
Lines 40-42 add the payload to the tabbed pane, without any label.
Lines 44-53 create a small panel that will contain everything shown on the tab itself. This area is circled in red below.

Lines 55-68 create the close-tab button.
Lines 70-76 put all the bits together in the tab panel.
Line 79 puts the panel into the tab using the aforementioned setComponentAt method.
Lines 81-90 add a listener to the button that will close the tab when it is clicked.
Line 91 brings the new tab to the front.
Optional bonus…
Lines 95-116 add a keystroke handler so you can close the tab with Control-W.
Additional Notes
As I mentioned earlier, I didn’t want to have to make my own “unselected” version of the button, since I might be trying different variations of buttons. Fortunately, JGoodies provides a handy-dandy utility class that they use to make gray icons for their toolbars. In line 62 I use the RGBGrayFilter class to make a grayed-out version of the button icon.
The actual placement of the labels on the tab depends heavily on the look and feel you are using. I found that I had to tweak the settings of the tab panel’s layout and border in order for it to look nice when using the Plastic look and feel. If I were using a different look and feel, I would probably have to tweak the borders.
I noticed that when a tab is selected, its label is raised up a few pixels. Without tweaking borders and opacity, this resulted in the tab panel being painted over the top edge of the tab, resulting in behavior like this:

Similar strange behavior happens if you omit some of the other border tweaks.
The action listener I use is quite simplistic. In a real application, you probably want to ask the content of the tab if it can be closed, to make sure the user doesn’t lose any changes.
Some apps, like Safari, hide the button altogether until the mouse hovers over it. To achieve this, simply use a blank icon for the normal (non rollover) button icon.
Finally, if you use the Control-W tab close hot key, note that some components (I’m looking at you, JTable), swallow keystrokes in ways that make it difficult for higher components to see the keystroke. In addition, if you run your app on a Mac, you would probably want to map the close command to Command-W instead of Control-W.
I hope this helps somebody!



No. 1 — January 6th, 2013 at 5:12 am
Really very nice post … !!! Good help!!
But one thing … Why is it not properly working with JTabbedPane#SCROLL_TAB_LAYOUT option !!????
No. 2 — January 10th, 2013 at 2:35 am
Extremely useful..many thanks for posting!!!
No. 3 — February 10th, 2013 at 7:10 pm
Thank you a lot. Really well explained! It has helped a lot!
No. 4 — March 1st, 2013 at 10:46 pm
Thank you…
Try to add more examle for JavaSE Plz….
No. 5 — April 6th, 2013 at 10:15 am
Great! Helped me a lot. thanx
No. 6 — April 18th, 2013 at 1:00 pm
Hey! Can someone figure out a way to do it from within the tabbedpane class? I think that would be very interesting, don’t you?
No. 7 — April 18th, 2013 at 1:04 pm
Very nice and useful example, by the way.
No. 8 — May 19th, 2013 at 11:08 pm
Thank! Very good!