11.10 拖放功能 11.10.1 拖放目标

拖放是非常常见的操作,人们经常会通过拖放操作来完成复制、剪切功能,但这种复制、剪切操作无须剪贴板支持,程序将数据从拖放源直接传递给拖放目标。这种通过拖放实现的复制、剪切效果也被称为复制、移动。

人们在拖放源中选中一项或多项元素,然后用鼠标将这些元素拖离它们的初始位置,当拖着这些元素在拖放目标上松开鼠标按键时,拖放目标将会查询拖放源,进而访问到这些元素的相关信息,并会相应地启动一些动作。例如,从Windows资源管理器中把一个文件图标拖放到WinPad图标上,WinPad将会打开该文件。如果在Eclipse中选中一段代码,然后将这段代码拖放到另一个位置,系统将会把这段代码从初始位置删除,并将这段代码放到拖放的目标位置。

除此之外,拖放操作还可以与三种键组合使用,用以完成特殊功能。

  • 与Ctrl键组合使用:表示该拖放操作完成复制功能。例如,可以在Eclipse中通过拖放将一段代码剪切到另一个地方,如果在拖放过程中按住tl键,系统将完成代码复制,而不是剪切。
  • 与Shift键组合使用:表示该拖放操作完成移动功能。有些时候直接拖放默认就是进行复制,例如,从Windows资源管理器的一个路径将文件图标拖放到另一个路径,默认就是进行文件复制。此时可以结合Shift键来进行拖放操作,用以完成移动功能。
  • 与Ctrl、Shift键组合使用:表示为目标对象建立快捷方式(在UNIX等平台上称为链接)。

11.10.1 拖放目标

在GUI界面中创建拖放目标非常简单,AWT提供了DropTarget类来表示拖放目标,可以通过该类提供的如下构造器来创建一个拖放目标。

  • DropTarget(Component c,int ops,DropTargetListener dtl):将c组件创建成一个拖放目标,该拖放目标默认可接受ops值所指定的拖放操作。其中DropTargetListener是拖放操作的关键,它负责对拖放操作做出相应的响应。ops可接受如下几个值。
    • DnDConstants.ACTION_COPY:表示“复制”操作的int值。
    • DnDConstants.ACTION_COPY_OR_MOVE:表示“复制”或“移动”操作的int值。
    • DnDConstants.ACTION_LINK:表示建立“快捷方式”操作的int值。
    • DnDConstants.ACTION_MOVE:表示“移动”操作的int值。
    • DnDConstants.ACTION_NONE:表示无任何操作的int值。

例如,下面代码将一个JFrame对象创建成拖放目标。

1
2
// 将当前窗口创建成拖放目标
new DropTarget(jf,DnDConstants.ACTION_COPY,new ImageDropTargetListener());

正如从上面代码中所看到的,创建拖放目标时需要传入一个DropTargetListener监听器,该监听器负责处理用户的拖放动作。该监听器里包含如下5个事件处理器。

  • dragEnter(DropTargetDragEvent dtde):当光标进入拖放目标时将触发DropTargetListener监听器的该方法。
  • dragExit(DropTargetEvent dtde):当光标移出拖放目标时将触发DropTargetListener监听器的该方法。
  • dragOver(DropTargetDragEvent dtde):当光标在拖放目标上移动时将触发DropTargetListener监听器的该方法。
  • drop(DropTargetDropEvent dtde):当用户在拖放目标上松开鼠标键,拖放结束时将触发DropTargetListener监听器的该方法。
  • dropActionChanged(Drop TargetDragEvent dtde):当用户在拖放目标上改变了拖放操作,例如按下或松开了Ctrl等辅助键时将触发DropTargetListener监听器的该方法。

通常程序不想为上面每个方法提供响应,即不想重写DropTargetListener监听器的每个方法,只想重写我们关心的方法,可以通过继承DropTargetAdapter适配器来创建拖放监听器。下面程序利用拖放目标创建了一个简单的图片浏览工具,当用户把一个或多个图片文件拖入该窗口时,该窗口将会自动打开每个图片文件。

import java.util.List;
import java.io.*;

import java.awt.Dimension;
import java.awt.Image;
import java.awt.event.*;
import java.awt.dnd.*;
import javax.imageio.*;
import java.awt.datatransfer.*;
import javax.swing.*;

public class DropTargetTest
{
    final int DESKTOP_WIDTH = 480;
    final int DESKTOP_HEIGHT = 360;
    final int FRAME_DISTANCE = 30;
    JFrame jf = new JFrame("测试拖放目标——把图片文件拖入该窗口");
    // 定义一个虚拟桌面
    private JDesktopPane desktop = new JDesktopPane();
    // 保存下一个内部窗口的坐标点
    private int nextFrameX;
    private int nextFrameY;
    // 定义内部窗口为虚拟桌面的1/2大小
    private int width = DESKTOP_WIDTH / 2;
    private int height = DESKTOP_HEIGHT / 2;
    public void init()
    {
        desktop.setPreferredSize(new Dimension(DESKTOP_WIDTH
            , DESKTOP_HEIGHT));
        // 将当前窗口创建成拖放目标
        new DropTarget(jf, DnDConstants.ACTION_COPY
            , new ImageDropTargetListener());
        jf.add(desktop);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.pack();
        jf.setVisible(true);
    }
    class ImageDropTargetListener extends DropTargetAdapter
    {
        public void drop(DropTargetDropEvent event)
        {
            // 接受复制操作
            event.acceptDrop(DnDConstants.ACTION_COPY);
            // 获取拖放的内容
            Transferable transferable = event.getTransferable();
            DataFlavor[] flavors = transferable.getTransferDataFlavors();
            // 遍历拖放内容里的所有数据格式
            for (int i = 0; i < flavors.length; i++)
            {
                DataFlavor d = flavors[i];
                try
                {
                    // 如果拖放内容的数据格式是文件列表
                    if (d.equals(DataFlavor.javaFileListFlavor))
                    {
                        // 取出拖放操作里的文件列表
                        List fileList = (List)transferable
                            .getTransferData(d);
                        for (Object f : fileList)
                        {
                            // 显示每个文件
                            showImage((File)f , event);
                        }
                    }
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
                // 强制拖放操作结束,停止阻塞拖放目标
                event.dropComplete(true);    // ①
            }
        }
        // 显示每个文件的工具方法
        private void showImage(File f , DropTargetDropEvent event)
            throws IOException
        {
            Image image = ImageIO.read(f);
            if (image == null)
            {
                // 强制拖放操作结束,停止阻塞拖放目标
                event.dropComplete(true);     // ②
                JOptionPane.showInternalMessageDialog(desktop
                    , "系统不支持这种类型的文件");
                // 方法返回,不会继续操作
                return;
            }
            ImageIcon icon = new ImageIcon(image);
            // 创建内部窗口显示该图片
            JInternalFrame iframe = new JInternalFrame(f.getName()
                , true , true , true , true);
            JLabel imageLabel = new JLabel(icon);
            iframe.add(new JScrollPane(imageLabel));
            desktop.add(iframe);
            // 设置内部窗口的原始位置(内部窗口默认大小是0X0,放在0,0位置)
            iframe.reshape(nextFrameX, nextFrameY, width, height);
            // 使该窗口可见,并尝试选中它
            iframe.show();
            // 计算下一个内部窗口的位置
            nextFrameX += FRAME_DISTANCE;
            nextFrameY += FRAME_DISTANCE;
            if (nextFrameX + width > desktop.getWidth())
                nextFrameX = 0;
            if (nextFrameY + height > desktop.getHeight())
                nextFrameY = 0;
        }
    }
    public static void main(String[] args)
    {
        new DropTargetTest().init();
    }
}

上面程序中粗体字代码部分创建了一个拖放目标,创建拖放目标很简单,关键是需要为该拖放目标编写事件监听器。上面程序中采用ImageDropTargetListener对象作为拖放目标的事件监听器,该监听器重写了drop()方法,即当用户在拖放目标上松开鼠标按键时触发该方法。drop()方法里通过DropTargetDropEvent对象的getTransferable()方法取出被拖放的内容,一旦获得被拖放的内容后,程序就可以对这些内容进行适当处理,本例中只处理被拖放格式是DataFlavor.javaFileListFlavor(文件列表)的内容,处理方法是把所有的图片文件使用内部窗口显示出来。

运行该程序时,只要用户把图片文件拖入该窗口,程序就会使用内部窗口显示该图片。

注意:

上面程序中①②处的event.dropComplete(true);代码用于强制结束拖放事件,释放拖放目标的阻塞,如果没有调用该方法,或者在弹出对话框之后调用该方法,将会导致拖放目标被阻塞。在对话框被处理之前,拖放目标窗口也不能获得焦,点,这可能不是程序希望的效果,所以程序在弹出内部对话框之前强制结束本次拖放操作(因为文件格式不对),释放拖放目标的阻塞。

上面程序中只处理DataFlavor.javaFileListFlavor格式的拖放内容;除此之外,还可以处理文本格式的拖放内容,文本格式的拖放内容使用DataFlavor.stringFlavor格式来表示。

更复杂的情况是,可能被拖放的内容是带格式的内容,如text/html和text/rtf等。为了处理这种内容,需要选择合适的数据格式,如下代码所示:

1
2
3
4
5
6
7
// 如果被拖放的内容是text/html格式的输入流
if (d.isMimeTypeEqual("text/html")&& d.getRepresentationClass()==Inputstream.class) {
String charset = d.getParameter ("charset");
InputStreamReader reader = new InputStreamReader(transferable.getTransferData(d),charset);
//使用IO流读取拖放操作的内容
...
}

关于如何使用I0流来处理被拖放的内容,读者需要参考本书第15章的内容。