Swing Drag & Drop

“JFC Swing Tutorial, The: A Guide to Constructing GUIs, Second Editon”

image

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;
}

posted @ 2013-09-09 22:26  莫忆往西  阅读(600)  评论(0编辑  收藏  举报