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 | // 将当前窗口创建成拖放目标 |
正如从上面代码中所看到的,创建拖放目标时需要传入一个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 | // 如果被拖放的内容是text/html格式的输入流 |
关于如何使用I0流来处理被拖放的内容,读者需要参考本书第15章的内容。