dnd是drag and drop的缩写.
java中的dnd主要涉及到3个类:TransferHandler(用来处理数据的拖放过程),Transferable(用来包装拖放的数据),和DataFlavor(用来表示拖放的数据的类型).下面来介绍这3个类的方法

1.javax.swing.TransferHandler
它有两个构造函数:
TransferHandler() 子类的便捷构造方法。

TransferHandler(String property) 构造一个通过剪贴板或拖放操作可以将 Java Bean 属性从一个组件传输到另一个组件的传输处理程序。
如,JLabel和JTextField都有text这个属性,所以可以很简单地实现从JTextField里拖文本到JLabel里,改变它的文本.下面是一个例子
在textField里输入文本后,往label里拖,label的文本就变为textField里的文本了.如果要实现从label往textField里拖,还要另外的方法,先不说

import  java.awt. * ;
import  javax.swing. * ;
import  java.awt.event. * ;
import  javax.swing.event. * ;

class  LabelDnd 
{
 JFrame mainFrame;
 JPanel mainPanel;
 JLabel label;
 JTextField textField;
  public  LabelDnd() {
  mainFrame  =   new  JFrame (  );
  mainPanel  =   new  JPanel (  new  BorderLayout() );
  label  =   new  JLabel ( " label " );
   // 这里调用了TransferHandler的第二个构造函数,参数是一个Java Bean 属性
  label.setTransferHandler(  new  TransferHandler( " text " ) );
  textField  =   new  JTextField( 20 );
   // 打开textField自带的拖放功能
  textField.setDragEnabled(  true  );
  mainPanel.add( label,BorderLayout.PAGE_START );
  mainPanel.add( textField,BorderLayout.PAGE_END  );
  mainFrame.getContentPane().add( mainPanel );
  mainFrame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
  mainFrame.pack();
  mainFrame.setLocationRelativeTo( null );
  mainFrame.setVisible(  true  );
 }
  public   static   void  main(String[] args) 
 {
   new  LabelDnd();
 }
}


再看一个例子,JLabel还有foreground这个属性.所以也可以很容易实现拖放改变它的前景色.在colorChooser里选一种颜色以后,在样本面板里往label一拖,label的文字的颜色就变了

import  java.awt. * ;
import  javax.swing. * ;
import  java.awt.event. * ;
import  javax.swing.event. * ;

class  LabelDnd2 
{
 JFrame mainFrame;
 JPanel mainPanel;
 JLabel label;
 JColorChooser colorChooser;
  public  LabelDnd2() {
  mainFrame  =   new  JFrame (  );
  mainPanel  =   new  JPanel ();
  colorChooser  =   new  JColorChooser ();
  colorChooser.setDragEnabled(  true  );
  label  =   new  JLabel ( "  i can accept color  " );
  label.setTransferHandler(  new  TransferHandler( " foreground " ) );
  mainPanel.add( colorChooser );
  mainPanel.add( label );
  mainFrame.getContentPane().add( mainPanel );
  mainFrame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
  mainFrame.pack();
  mainFrame.setLocationRelativeTo( null );
  mainFrame.setVisible(  true  );
 }
  public   static   void  main(String[] args) 
 {
   new  LabelDnd2();
 }
}

下面来看一些它的用于拖放的方法(还有一些方法一般用于绑定键盘输入,暂且不谈)

DragGestureRecognizer 是一个针对与平台相关的侦听器规范的抽象基类,它可以与特定 Component 关联以标识与平台相关的拖动开始动作。

可从与特定 Component 关联的 DragSource,或通过其 createDragGestureRecognizer() 方法从 Toolkit 对象获得合适的 DragGestureRecognizer 子类。

DragGestureRecognizer 一旦与特定的 Component 关联,就会在该 Component 上注册合适的侦听器接口,以跟踪传递给该 Component 的输入事件。

DragGestureRecognizer 一旦将该 Component 上的事件顺序标识为拖动开始动作,它就会通过调用其 gestureRecognized() 方法来通知其单播传递的 DragGestureListener

当具体的 DragGestureRecognizer 实例在其关联的 Component 上检测到拖动开始动作时,它将激发一个 DragGestureEvent,并针对 DragGestureListener 事件将其传递给在其单播传递事件源上注册的 DragGestureListener。此 DragGestureListener 负责使关联的 DragSource 开始 Drag 和 Drop 操作(如果合适)。

 

DragGestureRecognizer中:
nt getSourceActions(JComponent c) 返回拖放源支持的传输操作的类型。有COPY,COPY_AND_MOVE,MOVE,NONE,分别指复制,复制和移动,移动和无传输操作;参数c是拖放源

Transferable createTransferable(JComponent c)  将你要在拖放中传输的数据包装入一个Tranferable类或它的子类里面.参数c是拖放源,里面包含了你要包装的数据,例如在一个JTextField里面的文本

boolean canImport(JComponent c, DataFlavor[] transferFlavors)  判断一个拖放动作里面的数据是否可被导入到c里面,参数c是拖放目标,另外一个参数是上一个方法createTransferable封装的Transferable包含的数据的数据类型.例如拖放动作里面的数据可能既有文本又有图片,transferFlavors就会包含有这两种数据类型.但是一个JTextField是不能接受图片数据的,所以要用这个方法来判断能不能在c上导入createTransferable产生的数据

boolean importData(JComponent comp, Transferable t)  在判断完可以导入数据以后,就调用这个方法在comp上导入t里面包含的数据

void exportDone(JComponent source, Transferable data, int action)  如果你执行的是移动操作而不是拖复制,在导入完数据以后,要在拖放源上删除移动的数据,这样方法就是用来实现这个目的的.所以,这个方法不一定要重载.

void exportAsDrag(JComponent comp, InputEvent e, int action) 导致 Swing 拖动支持的启用。 comp是拖放源,包含要传输的数据,e一般是鼠标的拖事件,action就是前面提到的COPY,COPY_AND_MOVE,MOVE,NONE.

举例说明这个方法的用法.稍修改前面第一个例子就可以实现从label里往textField里拖文本

import  java.awt. * ;
import  javax.swing. * ;
import  java.awt.event. * ;
import  javax.swing.event. * ;

class  LabelDnd 
{
 JFrame mainFrame;
 JPanel mainPanel;
 JLabel label;
 JTextField textField;
  public  LabelDnd() {
  mainFrame  =   new  JFrame (  );
  mainPanel  =   new  JPanel (  new  BorderLayout() );
  label  =   new  JLabel ( " label " );
  label.setTransferHandler(  new  TransferHandler( " text " ) );
  label.addMouseListener(  new  MouseAdapter(){
    public   void  mousePressed( MouseEvent e ){
    JComponent c  =  (JComponent)e.getSource();
    TransferHandler handler  =  c.getTransferHandler();
    handler.exportAsDrag(c,e,TransferHandler.COPY); // 调用了exportAsDrag
   }
  } );
  textField  =   new  JTextField( 20 );
  textField.setDragEnabled(  true  );
  mainPanel.add( label,BorderLayout.PAGE_START );
  mainPanel.add( textField,BorderLayout.PAGE_END  );
  mainFrame.getContentPane().add( mainPanel );
  mainFrame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
  mainFrame.pack();
  mainFrame.setLocationRelativeTo( null );
  mainFrame.setVisible(  true  );
 }
  public   static   void  main(String[] args) 
 {
   new  LabelDnd();
 }
}


2.java.awt.datatransfer.Transferable
这个接口用作包装要传输的数据.包装的数据一般作为实现它的类的属性

Object getTransferData(DataFlavor flavor)  返回一个对象,该对象表示将要被传输的数据.参数表示要返回的数据类型.例如,前面提到,你在一个组件上拖动,产生的Transferable可能既有文字又有图片,那么就可以用这个方法只提取其中的文字或图片.这个方法一般会在TransferHandler的importData方法里被调用.

DataFlavor[] getTransferDataFlavors()  返回一个类型数组.程序怎么知道你的Transferable包含哪些数据类型呢?通过调用这个函数就可以知道你的Transferable包含什么了.这个方法的返回值一般会作为参数传入TransferHandler 的canImport方法里

boolean isDataFlavorSupported(DataFlavor flavor) 判断你的Transferable是否包含有flavor指定的数据

3.java.awt.datatransfer.DataFlavor
这个类用来表示数据类型.方法比较多.但是,一般简单应用只要用到它的构造函数
DataFlavor() 和 DataFlavor(Class<?> representationClass, String humanPresentableName)
当你要传输自己定义的类时,这个类非常有用.

下面用例子说明以上类和接口的用法.

import  java.awt. * ;
import  javax.swing. * ;
import  java.awt.event. * ;
import  javax.swing.event. * ;
import  java.awt.datatransfer. * ;
import  java.io. * ;
class   PictureDnd
{
 JFrame mainFrame;
 JPanel mainPanel;
 PictureComponent[] pictures;
  public  PictureDnd() {
  mainFrame  =   new  JFrame (  );
  mainPanel  =   new  JPanel (  new  GridLayout( 2 , 2 ) );
   // PictureComponent是一个自定义的类
  pictures  =   new  PictureComponent[ 4 ]; 
  pictures[ 0 ]  =   new  PictureComponent(  new  ImageIcon( " images/Adele.jpg " ).getImage() );
  pictures[ 1 ]  =   new  PictureComponent(  new  ImageIcon( " images/Anya.jpg " ).getImage() );
  pictures[ 2 ]  =   new  PictureComponent(  null  );
  pictures[ 3 ]  =   new  PictureComponent(  null  );
  mainPanel.add( pictures[ 0 ] );
  mainPanel.add( pictures[ 1 ] );
  mainPanel.add( pictures[ 2 ] );
  mainPanel.add( pictures[ 3 ] );
  mainPanel.setBorder(BorderFactory.createEmptyBorder( 20 , 20 , 20 , 20 ));
  mainFrame.getContentPane().add( mainPanel );
  mainFrame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
  mainFrame.setSize( 350 , 400 );
  mainFrame.setLocationRelativeTo( null );
  mainFrame.setVisible(  true  );
 }
  private   class  PictureComponent  extends  JComponent 
   implements  FocusListener,MouseListener,MouseMotionListener{
  Image image;
   public  PictureComponent( Image image ){
    this .image  =  image;
   setPreferredSize(  new  Dimension( 125 , 125 ) );
   setFocusable(  true  );
   setTransferHandler(  new  PictureTransferHandler() );
   addFocusListener( this );
   addMouseListener( this );
   addMouseMotionListener( this );
  }
   public  Image getImage(){
    return  image;
  }
   public   void  setImage( Image image ){
    this .image  =  image;
   repaint();
  }
   // 绘制我们的类,简单的在上面画一幅图
   public   void  paintComponent( Graphics graphics ){
   Graphics g  =  graphics.create();
   g.setColor( Color.white );
   g.fillRect( 0 , 0 , image  !=   null   ?  image.getWidth( this ): 125 ,image  !=   null   ?  image.getHeight( this ): 125 );
    if ( image  !=   null  )
    g.drawImage(image, 0 , 0 , this );
    if ( isFocusOwner() )
    g.setColor(Color.red);
    else
    g.setColor(Color.black);
    // 画边框,如果是焦点获得者,边框为红色,否则为黑色
   g.drawRect( 0 , 0 , image  !=   null   ?  image.getWidth( this ): 125 ,image  !=   null   ?  image.getHeight( this ): 125 );
   g.dispose();
  }
   public   void  focusGained( FocusEvent e ){
   repaint();
  }
   public   void  focusLost( FocusEvent e ){
   repaint();
  }
   public   void  mouseClicked(MouseEvent e) {
   requestFocusInWindow();
  }
   public   void  mouseEntered(MouseEvent e) { }
   public   void  mouseExited(MouseEvent e) { }
   public   void  mousePressed(MouseEvent e) { }
   public   void  mouseReleased(MouseEvent e) { }

   public   void  mouseDragged(MouseEvent e){
   JComponent c  =  (JComponent)e.getSource();
   TransferHandler handler  =  c.getTransferHandler();
    // 调用exportAsDrag
   handler.exportAsDrag(c,e,TransferHandler.COPY);
  }
   public   void  mouseMoved(MouseEvent e){}

 }
  private   class  TransferablePicture  implements  Transferable{ // 一个用来包装数据的类
// 我们的类只包含有图片类型的数据.DataFlavor.imageFlavor 是DataFlavor定义的一个DataFlavor类型,因为图片常用,所以它自带有这种类型,不用我们自己定义.以后再看如何自己定义
  DataFlavor flavors[]  =  { DataFlavor.imageFlavor };
  Image image;
   public  TransferablePicture( Image image ){
    this .image  =  image;
  }
   public  DataFlavor[] getTransferDataFlavors(){ // 返回我们的Transferable包含哪些数据类型
    return  flavors;
  }
   public  Object getTransferData(DataFlavor flavor){ // 根据参数返回数据
    if ( flavor.equals(DataFlavor.imageFlavor) )
     return  image;
    return   null ;
  }
   public   boolean  isDataFlavorSupported(DataFlavor flavor){
    return  flavor.equals(DataFlavor.imageFlavor);
  }
  
 }
  private   class  PictureTransferHandler  extends  TransferHandler{
   public  Transferable createTransferable( JComponent c ){
   PictureComponent pc  =  (PictureComponent)c;
    return   new  TransferablePicture( pc.getImage() ); // 调用构造函数以包装数据,将我们要包装的数据以参数形式传入
  }
   public   boolean  canImport(JComponent c,DataFlavor[] flavors){ // 判断是否可以导入数据
    for (DataFlavor flavor : flavors){
     if (flavor.equals(DataFlavor.imageFlavor))
      return   true ;
   }
    return   false ;
  }
   public   boolean  importData( JComponent c, Transferable t){ // 导入数据
    if ( canImport(c,t.getTransferDataFlavors() ) ){ // 如前所说,调用了getTransferDataFlavors() 
    PictureComponent pc  =  (PictureComponent)c;
     try { // 取得我们需要类型的的数据
     Image image  =  (Image)t.getTransferData(DataFlavor.imageFlavor);
     pc.setImage( image );
      return   true ;
    } catch ( UnsupportedFlavorException e ){
     e.printStackTrace();
    } catch ( IOException e ){
     e.printStackTrace();
    }    
   }
    return   false ;
  }
   public   void  exportDone(JComponent c, Transferable data,  int  action){
    // 传完数据以后,要判断是移动还是复制,然后决定要不要删除拖放源的数据
   PictureComponent picture  =  (PictureComponent)c;
    if ( action  ==  MOVE ){
    picture.setImage( null );
   }
  }
   public   int  getSourceActions(JComponent c){
    return  COPY_OR_MOVE;
  }
 }
  public   static   void  main(String[] args) 
 {
   new  PictureDnd();
 }
}

再来看如何使用剪贴板和同时传输多种数据类型

import  java.awt. * ;
import  javax.swing. * ;
import  java.awt.event. * ;
import  javax.swing.event. * ;
import  java.awt.dnd. * ;
import  java.awt.datatransfer. * ;
import  java.io. * ;
class  ClipboardTest2 
{
 JFrame mainFrame;
 JPanel mainPanel;
 JButton button;
 Clipboard cb; // 定义一个剪贴板
  public  ClipboardTest2() {
  mainFrame  =   new  JFrame (  );
  mainPanel  =   new  JPanel ();
  button  =   new  JButton ( " Button " );
  button.setIcon(  new  ImageIcon( " candle.png " ) );

  cb  =  Toolkit.getDefaultToolkit().getSystemClipboard(); // 取得系统的剪贴板
  button.addActionListener(  new  ActionListener(){
    public   void  actionPerformed( ActionEvent e){
    ButtonTextAndImageTransferable btait  =
      new  ButtonTextAndImageTransferable(button);
    // 设置剪贴板的内容,第一个参数是Transferable类型的,第二个是ClipboardOwner
    cb.setContents( btait,btait );
   }
  });

  mainPanel.add( button );
  mainFrame.getContentPane().add( mainPanel );
  mainFrame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
  mainFrame.pack();
  mainFrame.setLocationRelativeTo( null );
  mainFrame.setVisible(  true  );
 }
  public   static   void  main(String[] args) 
 {
   new  ClipboardTest2();
 }
}
class  ButtonTextAndImageTransferable  extends  ImageIcon  implements  Transferable,ClipboardOwner{
 DataFlavor[] flavors;
 JButton button;
  public   void  lostOwnership(Clipboard clipboard, Transferable contents){
  System.out.println(  " lostownership "  ); // ClipboardOwner里的唯一的方法
 }
  public  ButtonTextAndImageTransferable( JButton button){
     flavors  =   new  DataFlavor[ 2 ];
  flavors[ 0 ]  =   DataFlavor.stringFlavor;
  flavors[ 1 ]  =   DataFlavor.imageFlavor;
   this .button  =  button ;
 } // 这个数组说明我们的Transferable既有文字,又有图片
  public  DataFlavor[] getTransferDataFlavors(){  
   return  flavors;
 }
  public  Object getTransferData(DataFlavor flavor){
   if ( flavor.equals( flavors[ 0 ] ) ) // 根据参数决定返回的数据
  {
    return  button.getText();
  } else {
    if ( flavor.equals( flavors[ 1 ] ) ){
    ImageIcon icon  =  (ImageIcon)button.getIcon();
     return  icon.getImage();
   }
  }
   return   null ;
 }
  public   boolean  isDataFlavorSupported(DataFlavor flavor){
   if ( flavor.equals( flavors[ 0 ] )  ||
   flavor.equals( flavors[ 1 ] ))
     return   true ;
   return   false ;
 }
}

运行之后,点击button,图片和文字就复制到剪贴板,到word里,选菜单的编辑-选择性粘贴就可以粘贴图片或文字

====================================