Swing Drag & Drop
“JFC Swing Tutorial, The: A Guide to Constructing GUIs, Second Editon”
JTextComponent在创建的时候会安装cut/copy/paste的按键绑定,从而支持这几种快捷键操作。
传输机制的核心是TransferHandler类,它提供了了一个简单的机制在控件之间传输数据。被传输的数据被打包进一个实现了Transferable的接口中。对于默认支持拖拽的控件已经有一个默认的TransferHandler,也可以通过调用控件的setTransferHandler来设置新的Handler,从而替换控件默认的数据传输行为,例如 可以让文本控件传输颜色信息,而不是文本。
如果想要给一个控件添加拖拽操作,需要给控件添加addMouseListener(); 当鼠标被按下后,TransferHandler通过以COPY为参数调用exportAsDrag方法来初始化拖拽操作。
public class DragMouseAdapter extends MouseAdapter { public void mousePressed(MouseEvent e) { JComponent c = (JComponent)e.getSource(); TransferHandler handler = c.getTransferHandler(); handler.exportAsDrag(c, e, TransferHandler.COPY); } } label = new JLabel("I'm a Label!", SwingConstants.LEADING); label.setTransferHandler(new TransferHandler("text")); MouseListener listener = new DragMouseAdapter(); label.addMouseListener(listener);
TransferHandler中的importData和canImport方法处理导入数据; getSourceActions, createTransferable, exportDone方法处理导出数据。如果你不是CUT操作,就不需要实现exportDone方法,不用在传输结束后删除本地的数据。
public abstract class StringTransferHandler extends TransferHandler { protected abstract String exportString(JComponent c); protected abstract void importString(JComponent c, String str); protected abstract void cleanup(JComponent c, boolean remove); protected Transferable createTransferable(JComponent c) { return new StringSelection(exportString(c)); } public int getSourceActions(JComponent c) { return COPY_OR_MOVE; } public boolean importData(JComponent c, Transferable t) { if (canImport(c, t.getTransferDataFlavors())) { try { String str = (String)t.getTransferData(DataFlavor.stringFlavor); importString(c, str); return true; } catch (UnsupportedFlavorException ufe) { } catch (IOException ioe) { } } return false; } protected void exportDone(JComponent c, Transferable data, int action){ cleanup(c, action == MOVE); } public boolean canImport(JComponent c, DataFlavor[] flavors) { for (int i = 0; i < flavors.length; i++) { if (DataFlavor.stringFlavor.equals(flavors[i])) { return true; } } return false; } }
其中的StringSelection类实现了Transferable接口,负责将传输数据进行打包。一个子类实现:
public class ListTransferHandler extends StringTransferHandler { private int[] indices = null; private int addIndex = -1; //Location where items were added private int addCount = 0; //Number of items added. //Bundle up the selected items in the list //as a single string, for export. protected String exportString(JComponent c) { JList list = (JList)c; indices = list.getSelectedIndices(); Object[] values = list.getSelectedValues(); StringBuffer buff = new StringBuffer(); for (int i = 0; i < values.length; i++) { Object val = values[i]; buff.append(val == null ? "" : val.toString()); if (i != values.length - 1) { buff.append("\n"); } } return buff.toString(); } //Take the incoming string and wherever there is a //newline, break it into a separate item in the list. protected void importString(JComponent c, String str) { JList target = (JList)c; DefaultListModel listModel = (DefaultListModel)target.getModel(); int index = target.getSelectedIndex(); //Prevent the user from dropping data back on itself. //For example, if the user is moving items #4,#5,#6 and #7 and //attempts to insert the items after item #5, this would //be problematic when removing the original items. //So this is not allowed. if (indices != null && index >= indices[0] - 1 && index <= indices[indices.length - 1]) { indices = null; return; } int max = listModel.getSize(); if (index < 0) { index = max; } else { index++; if (index > max) { index = max; } } addIndex = index; String[] values = str.split("\n"); addCount = values.length; for (int i = 0; i < values.length; i++) { listModel.add(index++, values[i]); } } //If the remove argument is true, the drop has been //successful and it's time to remove the selected items //from the list. If the remove argument is false, it //was a Copy operation and the original list is left //intact. protected void cleanup(JComponent c, boolean remove) { if (remove && indices != null) { JList source = (JList)c; DefaultListModel model = (DefaultListModel)source.getModel(); //If we are moving items around in the same list, we //need to adjust the indices accordingly, since those //after the insertion point have moved. if (addCount > 0) { for (int i = 0; i < indices.length; i++) { if (indices[i] > addIndex) { indices[i] += addCount; } } } for (int i = indices.length - 1; i >= 0; i--) { model.remove(indices[i]); } } indices = null; addCount = 0; addIndex = -1; } }
注意在删除原有元素时,需要根据被拖走元素位置,对要删除的元素索引进行适配,以免把新加入的元素删掉了。
DataFlavor类用来描述TransferHandler和Transferable支持的数据类型。系统预定义了三种:
imageFlavor----java.awt.Image
stringFlavor----java.lang.String
javaFileListFlavor-----java.io.File objects in a java.util.List
创建自定义的DataFlavor:
DataFlavor(Class representationClass, String humanPresentableName); 如
new DataFlavor(ArrayList.class, "ArrayList");
new DataFlavor(int[].class, "Integer Array");
通过DataFlavor方式传输的数据使用对象的serialization,因此传输的数据对象需要实现Serializable接口,否则会有NotSerializableException。
通过DataFlavor(Class, String)方式创建的DataFlavor可以在应用程序间传递数据,包括本地应用程序。如果想创建一个只在应用程序内部传输数据,可以使用javaJVMLoacalObjectMimeType 和DataFlavor(String)方法。例如创建一个Color类的DataFlavor:
String colorType = DataFlavor.javaJVMLocalOjectMimeType + “;class=java.awt.Color”;
DataFlavor colorFlavor = new DataFlavor(colorType);
整型数组可以用:
new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=\"" + int[].class.getName() + "\"");
一个包含有特殊字符如[和;的MIME type,需要将这些特殊字符放在引号中。
一个Transferable对象可以支持多个flavors。在实现Transferable的getTransferDataFlavors方法时,创建一个DataFlavors的数组,将最想支持的数据类型放在最前面。
自定义控件实现拖拽需要实现鼠标的MouseMotionListener接口。在mouseDragged方法中,判断光标在鼠标按下的条件下移动了n个像素时,就调用TransferHandler来初始化拖拽操作,同时可判断是否有按下Ctrl来确定是复制还是移动。
MouseEvent firstMouseEvent = null; public void mousePressed(MouseEvent e) { //Don't bother to drag if there is no image. if (image == null) return; firstMouseEvent = e; e.consume(); } public void mouseDragged(MouseEvent e) { //Don't bother to drag if the component displays no image. if (image == null) return; if (firstMouseEvent != null) { e.consume(); //If they are holding down the control key, COPY rather than MOVE int ctrlMask = InputEvent.CTRL_DOWN_MASK; int action = ((e.getModifiersEx() & ctrlMask) == ctrlMask) ? TransferHandler.COPY : TransferHandler.MOVE; int dx = Math.abs(e.getX() - firstMouseEvent.getX()); int dy = Math.abs(e.getY() - firstMouseEvent.getY()); //Arbitrarily define a 5-pixel shift as the //official beginning of a drag. if (dx > 5 || dy > 5) { //This is a drag, not a click. JComponent c = (JComponent)e.getSource(); //Tell the transfer handler to initiate the drag. TransferHandler handler = c.getTransferHandler(); handler.exportAsDrag(c, firstMouseEvent, action); firstMouseEvent = null; } } } public void mouseReleased(MouseEvent e) { firstMouseEvent = null; }