The Source for Java Technology Collaboration
User: Password:



Joerg Plewe

Joerg Plewe's Blog

'Close' icons on a JTabbedPane w/o UI interference

Posted by herkules on October 29, 2005 at 04:18 AM | Comments (7)

Many apps require a 'close' icon on a tab of a JTabbedPane. The solutions I've seen so far all require manipulation of the L&F classes which can be considered bad in many respects.

There is another option that works without interference with the UI classes. It relies on a special implementation of Icon that is sensitive to the mouse itself. Such an icon can be set for each tab (setIconAt()). This solution is far from perfect. E.g. the close icon always appears on the left side of the tabs text. But it is simple, easy to use and works with all L&Fs. The schema might be useful in other areas of icon usage as well.

How does it work?

The implementation of CloseTabIcon uses are common Icon and delegates all method calls to it. Just, during paintIcon(), it remembers the last position the icon has been painted and additionally adds a MouseListener to the resp. Component. This mouselistener can test wether the mouse button has been pressed above the icon and perform the appropriate action then.

In order not to loose the possibility to set an icon to the tab that does not close it, I provide another utility class CombinedIcon. It implements Icon and delegates to two Icons given to its constructor. Using CombinedIcon and CloseTabIcon can create JTabbedPanes like this:




/*
 * TabCloseIcon.java
 */

package de.hardcode.util.swing;

import java.awt.Component;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JTabbedPane;

/**
 *
 * @author Herkules
 */
public class TabCloseIcon implements Icon
{
	private final Icon mIcon;
	private JTabbedPane mTabbedPane = null;
	private transient Rectangle mPosition = null;
	
	/**
	 * Creates a new instance of TabCloseIcon.
	 */
	public TabCloseIcon( Icon icon )
	{
		mIcon = icon;
	}
	
	
	/**
	 * Creates a new instance of TabCloseIcon.
	 */
	public TabCloseIcon()
	{
		this( new ImageIcon( TabCloseIcon.class.getResource("icons/closeTab.gif")) );
	}
	
	
	/**
	 * when painting, remember last position painted.
	 */
	public void paintIcon(Component c, Graphics g, int x, int y)
	{
		if( null==mTabbedPane )
		{
			mTabbedPane = (JTabbedPane)c;
			mTabbedPane.addMouseListener( new MouseAdapter()
			{
				@Override public void mouseReleased( MouseEvent e )
				{
					// asking for isConsumed is *very* important, otherwise more than one tab might get closed!
					if ( !e.isConsumed()  &&   mPosition.contains( e.getX(), e.getY() ) )
					{
						final int index = mTabbedPane.getSelectedIndex();
						mTabbedPane.remove( index );
						e.consume();
					}
				}
			});
		}
		
		mPosition = new Rectangle( x,y, getIconWidth(), getIconHeight() );
		mIcon.paintIcon(c, g, x, y );
	}
	
	
	/**
	 * just delegate
	 */
	public int getIconWidth()
	{
		return mIcon.getIconWidth();
	}
	
	/**
	 * just delegate
	 */
	public int getIconHeight()
	{
		return mIcon.getIconHeight();
	}
	
}
Find the sourcecode here and a usage sample here.

Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Hi, nice hack. I like it.

    I am using CombinedCollections, CombinedIcon and so on a lot myself. The idea of combining things to build up new functionality while still exposing only the simple original interface to the outside world is elegant.

    This one is related. Reordering Tabs in TabbedPanes is possible now easily with JTabbedPane when running jdk >= 1.5 (it was a real pain with 1.4).

    Sample code/webstart app can be found at:

    Drag'n'Drop JTabbedPane.

    Also worthwhile maybe by other free swing hack

    Collapsible Panels for Swing


    Thanks Joerg for sharing your thoughts on your blog. Greetings from Cologne, Germany

    Stepan

    Posted by: stepanrutz on November 02, 2005 at 02:25 AM

  • Very nice !
    I extended the Icon that it completely paints the Tab.
    So I paint the icon, the text and the closable button in the icon itself! Now I have the 'closable button' (the cross) at the right side of the label.

    Kees.

    Posted by: keeskuip on March 08, 2006 at 01:02 AM

  • Nice work. In your anonymous MouseAdapter, though, you should add the line:

    mTabbedPane.removeMouseListener(this);

    or else it will leak.

    Posted by: bruces1 on April 19, 2006 at 02:06 AM

  • bruces1, I cannot follow your suspect that the code leaks. The mouseadapter is only added once ( if(null==mTabbedPane) ... ) and will be GCed together with the tabbedpane.

    Posted by: herkules on April 19, 2006 at 02:45 AM

  • Nice work indeed, but you forgot about one important thing. The mTabbedPane _might_ have some border set, and borders tend to take up some space (have insets), so the effective area being checked for a valid click will be in such case moved away from the real mIcon's position. I handled it this way:

    int xOffset = 0;
    int yOffset = 0;
    // important: include the border of mTabbedPane (if there is one)
    if ( mTabbedPane.getBorder() != null ) {
    xOffset=mTabbedPane.getBorder().getBorderInsets(mTabbedPane).left;
    yOffset=mTabbedPane.getBorder().getBorderInsets(mTabbedPane).top;
    }
    mPosition = new Rectangle( x+xOffset,y+yOffset, getIconWidth(), getIconHeight() );


    Posted by: owczi on January 05, 2007 at 10:17 PM

  • Thank you for the hint, owczi. Have you tested it? IMHO the border doesn't matter, bc. the x,y coordinates are relative to the panel.

    I tested my original code with a border and it worked fine. Using your patch, it doesn't. Where is the misunderstanding?

    Posted by: herkules on January 06, 2007 at 08:51 AM

  • Yes, I have tested it with J2SE 1.5: http://owczi.net/swingtabs.png - a part of my BSc thesis project. It seemed weird to me, too. Maybe I missed some important detail, a strange border or inset property or something. I'll investigate it further in some free time.

    Posted by: owczi on April 08, 2007 at 07:39 PM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds