下面的Demo主要用Java Swing实现了JTabbedPane内的tab可以拖拽到另一个JTabbedPane里的功能。
其实只要是用DnDTabbedPane生成的instance, 它里面所属的tab就可以随意拖拽到另一个DnDTabbedPane。
key words: DragGestureListener, DragSourceListener, Transferable, DropTargetListener.
/*
* DnDTabbedPaneExe.java
* Description: Initialize the UI and run the test.
*/
import java.awt.BorderLayout;
import javax.swing.*;
public class DnDTabbedPaneExe
{
public DnDTabbedPaneExe()
{
initUI();
}
public void initUI()
{
JFrame test = new JFrame("Drag and Drop Tabs Test");
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.setLocation(400, 400);
test.setSize(1000, 450);
// Our DnDTabbedPane here.
DnDTabbedPane tabs = new DnDTabbedPane();
DnDTabbedPane tabs2 = new DnDTabbedPane();
JPanel panel = new JPanel();
panel.add(new JButton("1"));
panel.add(new JButton("123"));
ImageIcon icon = new ImageIcon("smallChrome.JPG");
tabs.addTab("Table",icon, initTable());
tabs.addTab("ButtonGroup",icon, panel);
tabs.addTab("JTextArea",icon, new JTextArea("2"));
tabs.addTab("JLabel",icon, new JLabel("3"));
tabs.addTab("Button",icon, new JButton("4"));
tabs.addTab("Table2",icon, initTable());
tabs2.addTab("Story", icon, new JTextArea("This is a story"));
tabs2.addTab("A Button",icon, new JButton("A"));
tabs2.addTab("B Button",icon, new JButton("B"));
test.add(initTree(), BorderLayout.WEST);
test.add(tabs, BorderLayout.EAST);
test.add(tabs2, BorderLayout.CENTER);
test.setVisible(true);
}
/*
* Main
*/
public static void main(String[] args)
{
DnDTabbedPaneExe frame = new DnDTabbedPaneExe();
}
// Methods initialize some tabs
/*
* Initialize JTree with default JTree example while new JTree()
*/
public class InitJTree extends JTree
{
public InitJTree()
{
super();
this.setAutoscrolls(true);
}
}
/*
* Return a JScrollPane with draggable JTree
*/
private JScrollPane initTree()
{
JTree myTree;
myTree = new JTree();
myTree.setDragEnabled(true);
myTree.setTransferHandler(new TreeTransferHandler());// -----TreeTransferHandler here-------
JScrollPane treePane = new JScrollPane(myTree);
return treePane;
}
/*
* Return a JScrollPane with table accept the draggable Tree nodes
*/
private JScrollPane initTable()
{
JTable table = new JTable(8, 3);
for (int i = 0; i < 8; i++)
{
table.setValueAt(i + ",0", i, 0);
}
table.setTransferHandler(new TableTransferHandler());// -----TableTransferHandler here------
return new JScrollPane(table);
}
}
/*
* DnDTabbedPane.java
* Description: Tabs of this DnDTabbedPane can be dragged and dropped.
*/
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.TextArea;
import java.awt.Window;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.tree.TreePath;
public class DnDTabbedPane extends JTabbedPane
{
private static ImageIcon _iconCloseRed = new ImageIcon("close-red.gif");
private static ImageIcon _iconCloseBlack = new ImageIcon("close-black.gif");
private static ImageIcon _iconIDE = new ImageIcon("IDE.JPG");
private static ImageIcon _iconChrome = new ImageIcon("chrome.PNG");
//private final static int BUTTON_HOT = 2;
private final static int BUTTON_NORMAL = 1;
private final static int BUTTON_NULL = 0;
private int _preSelectedTab = -1;
private int _preRolloverTab = -1;
private boolean _bClosed = true;
private int dragTabIndex = -1;
/**
* DnDTabbedPane class constructor: Here we initialize DragSourceListener
* and DragGestureListnener
*/
public DnDTabbedPane()
{
super();
//setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
setAutoscrolls(true);
initComponents();
// set JTabbedPane as DropTarget and DragSource here.
new DragSource().createDefaultDragGestureRecognizer(DnDTabbedPane.this, DnDConstants.ACTION_COPY_OR_MOVE, new DnDDragGestureListener());
new DropTarget(DnDTabbedPane.this, DnDConstants.ACTION_COPY_OR_MOVE, new DnDTargetListener(), true);
}
/**
*
* Override AddTab with arguments: title and Component to the JTabbedPane, add close button to tab
* @see javax.swing.JTabbedPane#addTab(java.lang.String, java.awt.Component)
*/
public void addTab(String title,Icon iconTab, Component comp)
{
add(comp);
int index = indexOfComponent(comp);
TabComponent tabComponent = new TabComponent(title, iconTab);
setTabComponentAt(index, tabComponent);
int indexTab = getSelectedIndex();
setTabCloseButtonStatus(indexTab, BUTTON_NORMAL);
}
/**
* Control the appearance of close buttons on tabs.
*/
private void initComponents()
{
if (_bClosed)
{
// If current component needs to support the Close Button, we should add the
// ChangeListener event to update the icon of Close Button according to the selected
// status.
addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
int indexTab = getSelectedIndex();
if (_preSelectedTab != indexTab)
{
// Remove the icon from previous selected tab
setTabCloseButtonStatus(_preSelectedTab, BUTTON_NULL);
// Set current selected tab to be BUTTON_NORMAL
setTabNormalByMouse(indexTab);
_preSelectedTab = indexTab;
}
else
{
//After moving, the original selected index in src is not changed,
//but a new tab is selected, we have to enable close button for
//this new tab.
setTabNormalByMouse(_preSelectedTab);
}
}
});
addMouseListener(new MouseAdapter()
{
public void mouseExited(MouseEvent e)
{
// If user move mouse from TabbedComponent to Close button, it will trigger the
// mouseExited event, so we need to add the !mouseOnCloseButton(_preRolloverTab)
// condition. otherwise icon will be sparkling obviously
//if (_preRolloverTab >= 0 && _preRolloverTab != getSelectedIndex() && !mouseOnCloseButton(_preRolloverTab))
if (_preRolloverTab >= 0 && _preRolloverTab != getSelectedIndex())
{
// If mouse exited TabbedPane and not on the close button and
// current tab is not selected, it needs to clean up previous tab icon.
setTabCloseButtonStatus(_preRolloverTab, BUTTON_NULL);
}
}
});
// If tab is too much, it will be shown by multilayer. The width of header
// will be bigger than that of tab component. At this time, if user move
// mouse to the empty area we need to set the rollovered tab to be normal.
addMouseMotionListener(new MouseAdapter()
{
public void mouseMoved(MouseEvent e)
{
int pointingTabIndex = tabForCoordinate(e.getPoint());
if (_preRolloverTab >= 0 && _preRolloverTab != pointingTabIndex )
{
// If mouse enter another tab, we need to update current close button
// and reset that of the leaving tab
if (_preRolloverTab != getSelectedIndex())
setTabCloseButtonStatus(_preRolloverTab, BUTTON_NULL);
//if _preRolloverTab == getSelectedIndex(), it will always be with a close button.
/*else
setTabCloseButtonStatus(_preRolloverTab, BUTTON_NORMAL);*/
}
if (pointingTabIndex >= 0)
{
setTabNormalByMouse(pointingTabIndex);
_preRolloverTab = pointingTabIndex;
}
}
});
}
}
//Methods control behaviors of close button on tab.
/**
* Set the tab of this index to be normal with a black close button.
*
* _button.setRolloverIcon(New ImageIcon("close-red.jpg")); is used to control
* the appearance when the mouse is over the close button.
*/
private void setTabNormalByMouse(int index)
{
setTabCloseButtonStatus(index, BUTTON_NORMAL);
}
/**
* Reset the icon of close button on the indexed tab.
*/
private void setTabCloseButtonStatus(int indexTab, int buttonStatus)
{
if (indexTab >= 0 && indexTab < getTabCount())
{
TabComponent tabComponent = (TabComponent) getTabComponentAt(indexTab);
if (tabComponent != null)
{
tabComponent.setCloseButtonStatus(buttonStatus);
}
}
}
/**
* According to the Point, get the related index of Tab.
*/
private int tabForCoordinate(Point p)
{
int index = getUI().tabForCoordinate(DnDTabbedPane.this, p.x, p.y);
return index;
}
/**
* Inner class DnDDragGestureListener implements DragGestureListener.
* Start the drag here.
*/
class DnDDragGestureListener implements DragGestureListener
{
public void dragGestureRecognized(DragGestureEvent e)
{
//Get the drag source panel size
int srcWidth = e.getComponent().getWidth();
int srcHeight = e.getComponent().getHeight();
System.out.println("Source size: " + srcWidth + "," + srcHeight);
Point tabPt = e.getDragOrigin();
dragTabIndex = indexAtLocation(tabPt.x, tabPt.y);
if (dragTabIndex < 0)
{
System.out.println("Please drag a tab");
return;
}
//GhostedDragImage image = new GhostedDragImage (e.getSource(),this,new Point(400,400),_iconIDE,new Point(5,5),true);
try
{
System.out.println("<<Tab drag start...>>");
Cursor cursor = getToolkit().createCustomCursor(_iconChrome.getImage(), new Point(0,0), "usr");
//dge.startDrag(cursor, t, this);
//e.startDrag(DragSource.DefaultMoveDrop,_iconIDE.getImage(),new Point(5,5), new DnDTransferable(), new DnDSourceListener());
e.startDrag(cursor, new DnDTransferable(), new DnDSourceListener());
}
catch (InvalidDnDOperationException idoe)
{
idoe.printStackTrace();
}
}
}
/**
* Inner class DnDTransferable implements Transferable.
* 1. Specify the data type we want to transfer.
* 2. Also specify the getTransferData(DataFloavor flavor) that will be invoked in target's drop.
*/
class DnDTransferable implements Transferable
{
DataFlavor FLAVOR = new DataFlavor(DataFlavor.javaRemoteObjectMimeType, "javaRemoteObjectMimeType");
DataFlavor[] flavors = {FLAVOR};
public Object getTransferData(DataFlavor flavor)
{
System.out.println("Enter Transferable");
if (flavor.isMimeTypeEqual(DataFlavor.javaRemoteObjectMimeType))
{
System.out.println("True: This is an javaRemoteObjectMimeType");
return DnDTabbedPane.this;
}
else
{
System.out.println("False: This is null");
return null;
}
}
public DataFlavor[] getTransferDataFlavors()
{
return flavors;
}
public boolean isDataFlavorSupported(DataFlavor flavor)
{
return flavor.isMimeTypeEqual(DataFlavor.javaRemoteObjectMimeType);
}
}
/**
* Inner class DnDSourceListener implements DragSourceListener:
* All actions when this panel
* is treated as a source while dragging
*/
class DnDSourceListener implements DragSourceListener
{
public void dragEnter(DragSourceDragEvent e)
{
Cursor cursor = getToolkit().createCustomCursor(_iconChrome.getImage(), new Point(0,0), "usr");
e.getDragSourceContext().setCursor(cursor);
//e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
}
public void dragExit(DragSourceEvent e)
{
Cursor cursor = getToolkit().createCustomCursor(_iconCloseRed.getImage(), new Point(0,0), "usr");
e.getDragSourceContext().setCursor(cursor);
//e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
}
public void dragOver(DragSourceDragEvent e)
{
/*Point tabPt = e.getLocation();
SwingUtilities.convertPointFromScreen(tabPt, DnDTabbedPane.this);
int currentIndex = indexAtLocation(tabPt.x, tabPt.y);
if(currentIndex<0)
{
System.out.println(currentIndex + "out of index");
Cursor cursor = getToolkit().createCustomCursor(_iconCloseRed.getImage(), new Point(0,0), "usr");
e.getDragSourceContext().setCursor(cursor);
}
else
{
Cursor cursor = getToolkit().createCustomCursor(_iconChrome.getImage(), new Point(0,0), "usr");
e.getDragSourceContext().setCursor(cursor);
}*/
}
public void dragDropEnd(DragSourceDropEvent e)
{
}
public void dropActionChanged(DragSourceDragEvent e)
{
}
}
/**
* Target Inner class DnDTargetListener: All actions when this panel
* is treated as a target while dragging.
*/
class DnDTargetListener implements DropTargetListener
{
public void dragEnter(DropTargetDragEvent ev)
{
//Get the panel size of the current target.
Object obj = ev.getSource();
Component targetComp = ((DropTarget) obj).getComponent();
System.out.println("Current dropPanel size: " + targetComp.getWidth() + "," + targetComp.getHeight());
System.out.println("Drag enter target");
targetAcceptDrag(ev);
}
public void dragExit(DropTargetEvent ev)
{
}
public void dragOver(DropTargetDragEvent ev)
{
Object target = ev.getSource();
boolean targetIsJTabbedPane = ((DropTarget) target).getComponent() instanceof JTabbedPane;
if (targetIsJTabbedPane)
{
targetAcceptDrag(ev);
}
// System.out.println("(X,Y):" + ev.getLocation().x + "," + ev.getLocation().y);
}
public void dropActionChanged(DropTargetDragEvent ev)
{
targetAcceptDrag(ev);
}
public void drop(DropTargetDropEvent ev)
{
try
{
Object target = ev.getSource();
if (!bTargetIsTab(target))
{
return;
}
JTabbedPane srcTabbedPane;
String title;
Component component;
DataFlavor[] flavors = ev.getTransferable().getTransferDataFlavors();
System.out.println(flavors.length + " Flavor: " + flavors[0].toString());
JTabbedPane destTabbedPane = (JTabbedPane) ((DropTarget) target).getComponent();
//BasicTabbedPaneUI tabbedPaneUI = new BasicTabbedPaneUI (jtp);
for (int i = 0; i < flavors.length; i++)
{
if (flavors[i].isMimeTypeEqual(DataFlavor.javaRemoteObjectMimeType))// Accept tabs
{
System.out.println("<<Accept Tabs...>>");
// Get source tab's title and selectedComponent, add to the target
// JTabbedPane
Object obj = ev.getTransferable().getTransferData(flavors[i]);
if(! (obj instanceof JTabbedPane))
{
return;
}
srcTabbedPane = (JTabbedPane) obj;
title = getTitle(srcTabbedPane);
Icon icon = getIcon(srcTabbedPane);
component = srcTabbedPane.getSelectedComponent();
//Move original tab to the destination.
destTabbedPane.addTab(title,icon, component);
destTabbedPane.setSelectedIndex(destTabbedPane.getTabCount() - 1);
//If setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); we have to consider the condition that
//after adding tab to a lot of tabs, it has to auto scroll to the new added selected tab
//destTabbedPane.scrollRectToVisible(getSelectedTabBound(destTabbedPane));
//destTabbedPane.gett.scrollRectToVisible(getBounds());
}
else if (flavors[i].isFlavorSerializedObjectType())// Accept the tree nodes
{
ImageIcon nodeIcon = new ImageIcon("IDE.JPG");
System.out.println("<<Accept TreeNode...>>");
Object obj = ev.getTransferable().getTransferData(flavors[i]);
if(!(obj instanceof ArrayList))
{
return;
}
ArrayList<TreePath> treeList = new ArrayList<TreePath>((ArrayList<TreePath>) obj);
for (int j = 0; j < treeList.size(); j++)
{
destTabbedPane.addTab(treeList.get(j).getLastPathComponent().toString(), nodeIcon, new TextArea(treeList.get(j).toString()));
}
destTabbedPane.setSelectedIndex(destTabbedPane.getTabCount() - 1);
}
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
ev.dropComplete(true);
repaint();
}
//Methods help to drop
/**
* Accept the drag
*/
private void targetAcceptDrag(DropTargetDragEvent ev)
{
ev.acceptDrag(ev.getDropAction());
}
private boolean bTargetIsTab (Object target)
{
return ((DropTarget) target).getDropTargetContext().getComponent() instanceof JTabbedPane;
}
/**
* Get tab component's title from original tab
* @param JTabbedPane
* @return String
*/
private String getTitle(JTabbedPane tabbedPane)
{
return ((TabComponent)tabbedPane.getTabComponentAt(tabbedPane.getSelectedIndex())).getTitle();
}
/**
* Get tab component's Icon from original tab
* @param JTabbedPane
* @return Icon
*/
private Icon getIcon(JTabbedPane tabbedPane)
{
return ((TabComponent)tabbedPane.getTabComponentAt(tabbedPane.getSelectedIndex())).getIcon();
}
}
//Component to be used as tab component of JTabbedPane.
/**
* Component to be used as tab component of TabbedPane. It contains two JLabel to show the text
* and icon and a JButton to close the tab it belongs to
*/
class TabComponent extends JPanel
{
private static final long serialVersionUID = 2955071016071608002L;
private JButton _button;
private JLabel _labelTitle;
private JLabel _labelIcon;
private int _statusCloseButton = -1;
public TabComponent(final String tabTitle, final Icon icon)
{
super();
initComponents(tabTitle, icon);
}
/**
* Get the icon of the icon label.
* @return
*/
public Icon getIcon()
{
return _labelIcon.getIcon();
}
/**
* Get the value of title label.
* @return
*/
public String getTitle()
{
return _labelTitle.getText();
}
/**
* Update the value of title label.
*/
public void setTitle(String text)
{
_labelTitle.setText(text);
}
/**
* Update the status of close button.
*/
public void setCloseButtonStatus(int status)
{
if (_statusCloseButton != status)
{
setButtonIcon(status);
_statusCloseButton = status;
}
}
/**
* Update the close button's icon directly.
* BUTTON_HOT: Mouse is on the button.
* BUTTON_NORMAL: Mouse is on the tab header but not on the button.
* BUTTON_NULL: : Mouse is not on the tab header and button.
*/
private void setButtonIcon(int buttonStatus)
{
if (_button != null)
{
// If icon is same as previous, JDK will not reset its icon.
switch (buttonStatus)
{
/*case BUTTON_HOT:
_button.setIcon(_iconCloseRed);
break;*/
case BUTTON_NORMAL:
_button.setIcon(_iconCloseBlack);
break;
case BUTTON_NULL:
_button.setIcon(null);
}
}
}
/**
* Init components supposed to be on the tab
* @param tabTitle
* @param icon
*/
private void initComponents(final String tabTitle, final Icon icon)
{
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
// set the component to be transparent.
setOpaque(false);
setBorder(new EmptyBorder(0, 0, 0, 0));
// make tab icon
if (icon != null)
{
_labelIcon = new JLabel();
_labelIcon.setPreferredSize(new Dimension(19, 19));
_labelIcon.setIcon(icon);
add(_labelIcon);
add(Box.createRigidArea(new Dimension(5, 5)));
}
// make tab title
_labelTitle = new JLabel(tabTitle);
add(_labelTitle);
add(Box.createRigidArea(new Dimension(5, 5)));
// make close button
if (_bClosed)
{
_button = new JButton();
_button.setBorderPainted(false);
_button.setFocusable(false);
// make it to be transparent
_button.setContentAreaFilled(false);
_button.setPreferredSize(new Dimension(19, 19));
_button.setMargin(new Insets(0, 0, 0, 0));
_button.setRolloverIcon(_iconCloseRed);
_button.setToolTipText("Close");
_button.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
clickCloseButton(e, indexOfTabComponent(TabComponent.this));
}
});
_button.addMouseListener(new MouseAdapter()
{
@Override
public void mouseReleased(MouseEvent evt)
{
// If user right/middle click the mouse, we should convert mouse event to
// the TabbedPane to change selection. If user left click the mouse, we do
// not need to convert mouse event to the TabbedPanel because the
// actionListener of Button will be executed to close tab.
if (evt.getButton() != MouseEvent.BUTTON1)
convertMouseEventToTabbedPane(_button, evt);
}
@Override
public void mousePressed(MouseEvent evt)
{
// If user right/middle click the mouse, we should convert mouse event to
// the TabbedPane to change selection. If user left click the mouse, we do
// not need to convert mouse event to the TabbedPanel because the
// actionListener of Button will be executed to close tab.
if (evt.getButton() != MouseEvent.BUTTON1)
convertMouseEventToTabbedPane(_button, evt);
}
});
_button.addMouseMotionListener(new MouseAdapter()
{
public void mouseMoved(MouseEvent evt)
{
// convert the mouse move event to the the CLTabbedPane, it is used to keep the
// icon to be hot.
convertMouseEventToTabbedPane(_button, evt);
}
});
add(_button);
}
}
}
/**
* Dispatch event from specified component to current DnDTabbedPane.
*/
private void convertMouseEventToTabbedPane(Component comp, MouseEvent e)
{
MouseEvent evt = SwingUtilities.convertMouseEvent(comp, e, this);
dispatchEvent(evt);
}
/**
* This method is used to handle the click event on the close button.
*/
protected void clickCloseButton(ActionEvent e, int tabClosedIndex)
{
remove(tabClosedIndex);
}
}