浏览 5699 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2010-03-03
最后修改:2010-03-04
常见的, 把一个文件直接拖入程序里, 把item从一个list拖到另一个list 最几天在学习Swing的DnD机制, 被搞得一塌糊涂, 晕头转向的忙活了好几个晚上, 终于理出一点头绪来: 总体来说, DnD 包括这几个类: DragSource, DragGestureRecognizer, DragGestureListener, DragSourceAdapter, DropTargetAdapter, TransferHandler, Transferable, DataFlavor DragSoure: 环境变量, 生成DragGestureRecognizer DragGestureRecognizer: 环境变量, 绑定DragGestureListener给指定的Jcomponent DragGestureListener: 拖拽事件开始的源头 DragSourceAdapter: JComponent Source Listener: dragEnter, dragOver, dragExit, dragDropEnd DropTargetAdapter: JComponent Target Listener: dropEnter,, drapOver, dragExit, drop TransferHandler: drag & drop event handler, 从官方的源代码设计来看, DragSourceAdapter, DropTargetAdapter都包含在里面, 类似代理器的设计模式 Transferable: DnD事件的统一数据接口 DataFlavor:DnD事件的统一数据 先以简单的例子来演示DnD的机制 一 Drag 1.给 JTextField 添加DnD: JTextField txt1=new JTextField(); txt1.setDragEnabled(true); 虽然只需以上2行代码即可, 但其实Swing在背后给我们做了很多事情 首先, jframe生成的时候, DataSourse和DragGestureRecognizer已经生成, 并给所有textfield, textarea之类的控件添加了DnD事件, 其中所有的jtextfield控件都绑定给 "javax.swing.plaf.basic.BasicTextUI$TextTransferHandler@4a63d8", 而这个类的内部也生成有DragSoure和DropTarget所有接口的实现 然后, 当你在jtextfield里选中所有文字, 再拖拽的时候, 就会激活DnD事件 具体顺序(Drag): TransferHandler exportAsDrag->getSourceActions->createTransferabled->exportDone->dragGestureRecognized 多尝试几次, 你会发现, 其实不选中所有文件而直接拖拽, 也会激活DnD事件 TransferHandler dragGestureRecognized 更不可思议的是, 哪怕 txt1.setDragEnabled(false)也一样, 这是很有用的 2.给 Jlabel 添加DnD 官方给出的演示 /** * add label drag action with mousePressed * @author YS */ public class DragAndDropDemo2 { JFrame frame; Container container; JLabel lbl1; JLabel lbl2; JTextField txt1; JTextField txt2; JLabel lbl3; JTextArea txt3; public DragAndDropDemo2() { frame = new JFrame("DRAG AND DROP DEMO"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(null); container = frame.getContentPane(); frame.setBounds(100, 100, 400, 300); addComponent(); frame.setVisible(true); } private void addComponent() { lbl1 = new JLabel("text1"); lbl2 = new JLabel("text2"); txt1 = new JTextField(); txt2 = new JTextField(); lbl1.setBounds(10, 10, 40, 20); lbl2.setBounds(10, 40, 40, 20); txt1.setBounds(60, 10, 300, 20); txt2.setBounds(60, 40, 300, 20); txt1.setDragEnabled(true); txt2.setDragEnabled(true); container.add(lbl1); container.add(lbl2); container.add(txt1); container.add(txt2); addLabelDragEvent(lbl1); } private void addLabelDragEvent(JLabel lbl1) { lbl1.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { JComponent com=(JComponent)e.getSource(); TransferHandler hander=new TransferHandler("text"); com.setTransferHandler(hander); hander.exportAsDrag(com, e, DnDConstants.ACTION_COPY); } }); } public static void main(String[] args){ SwingUtilities.invokeLater(new Runnable() { public void run() { new DragAndDropDemo2(); } }); } } 由于label不能设计setDragEnabled(true), 所以需要人为的激活DnD事件, 一开始时, jframe并没有为jlabel绑定transferhanlder, 所以必须自己创建, 在new TransferHandler("text")的同时, drag & drop事件也已经生成了, 带"text"参数, 是创建能接受文本内容的handler, 具体的swing已经为我们做好了, 能用预设的最好都用预设的, 然后在mousePressed时激活exportAsDrag, 然后再转到 handler内部的dragGestureRecognized接口实现, swing已经帮我们作好获取label里面文本并生成transferable的工作了, 最终调用内部的startDrag开始拖拽 自己写另外一种实现 /** * add label drag action with dragGestureRecognizedListener * @author YS */ public class DragAndDropDemo3 { JFrame frame; Container container; JLabel lbl1; JLabel lbl2; JTextField txt1; JTextField txt2; JLabel lbl3; JTextArea txt3; public DragAndDropDemo3() { frame = new JFrame("DRAG AND DROP DEMO"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(null); container = frame.getContentPane(); frame.setBounds(100, 100, 400, 300); addComponent(); frame.setVisible(true); } private void addComponent() { lbl1 = new JLabel("text1"); lbl2 = new JLabel("text2"); txt1 = new JTextField(); txt2 = new JTextField(); lbl1.setBounds(10, 10, 40, 20); lbl2.setBounds(10, 40, 40, 20); txt1.setBounds(60, 10, 300, 20); txt2.setBounds(60, 40, 300, 20); txt1.setDragEnabled(true); txt2.setDragEnabled(true); container.add(lbl1); container.add(lbl2); container.add(txt1); container.add(txt2); addLabelDragEvent(lbl1); } private void addLabelDragEvent(JLabel lbl1) { DragSource ds=DragSource.getDefaultDragSource(); ds.createDefaultDragGestureRecognizer(lbl1, DnDConstants.ACTION_COPY, new DragGestureAdapter()); } public static void main(String[] args){ SwingUtilities.invokeLater(new Runnable() { public void run() { new DragAndDropDemo3(); } }); } class DragGestureAdapter implements DragGestureListener{ public void dragGestureRecognized(DragGestureEvent e) { System.out.println("Drag dragGestureRecognized"); JComponent c = (JComponent) e.getComponent(); Transferable t=createTransferable(c); e.startDrag(DragSource.DefaultCopyNoDrop, t, new DragAdapter()); } } class DragAdapter extends DragSourceAdapter{ @Override public void dragEnter(DragSourceDragEvent dsde) { Component com=(Component)dsde.getDragSourceContext().getComponent(); Set<Class> set=new LinkedHashSet<Class>(); set.add(JLabel.class); set.add(JTextField.class); if(set.contains(com.getClass())){ dsde.getDragSourceContext().setCursor(DragSource.DefaultCopyDrop); } } @Override public void dragExit(DragSourceEvent dse) { Component com=(Component)dse.getDragSourceContext().getComponent(); Set<Class> set=new LinkedHashSet<Class>(); set.add(JLabel.class); set.add(JTextField.class); if(set.contains(com.getClass())){ dse.getDragSourceContext().setCursor(DragSource.DefaultCopyNoDrop); } } } protected Transferable createTransferable(JComponent c) { System.out.println("DragGestureAdapter createTransferable"); //return super.createTransferable(c); String text=""; Component com=c; String type=com.getClass().getSimpleName(); if("JLabel".equals(type)){ text=type+((JLabel)com).getText(); }else if("JTextField".equals(type)){ text=type+": "+((JTextField)com).getText(); }else if("JTextArea".equals(type)){ text=type+": "+((JTextArea)com).getText(); } Transferable t=new StringSelection(text); return t; } } 这样也可以实现同样的效果, 原因在于: 其实mouseDragged的时候, 也会判断dragGestureRecognized, 因为这里给label绑定了dragGestureRecognized接口的实现, 所以DnD机制还是开始了, 从startDrag开始 这里要说一下鼠标的手势, 一开始应当指定的是DragSource.DefaultCopyNoDrop, 会是一个不可复制的鼠标, 如果指定DragSource.DefaultCopyDrop的话, 就是一个带+号的复制鼠标, 这样会误人视听, 鼠标在进入别的控件时, 会激发dragEnter事件, 在离开控件时, 激活dragExit, 要注意鼠标的手势 对比以上3个例子, 有几个比较重要的地方, jtextfield; setDragEnabled(true),全选并拖拽时激发transferhandler的exportDrag, 然后才是createTransferabled, dragDestrueGeconized, 最终startDrag, (startDrag是在dragDestrueGeconized里调用的) label(官方): 通过mousePress, 人为调用exportDrag, 然后的过程跟jtextfield一样 label(自定义): 通过绑定dragDestrueGeconized给jlabel, 然后dragDestrueGeconized在鼠标拖拽时被激活, transferable必须在dragDestrueGeconized里生成 所以, 可以得到, dragDestrueGeconized接口总会在鼠标拖拽(非mousePress)时 -----------------------------------------------Separator----------------------------------------------- 二 Drop Drop的触发顺序: 目标控件的DropTarget的 drop(松开鼠标左键)->获取目标控件的TransferHandler, 并调用它的canImport, 如果为true, 则执行acceptDrop ,然后调用importData, 最终dropComplete 仔细分析, 里面其实有2个地方容易混乱, 一个是droptarget的drop方法, 另一个是transferhandler的importData方法, 2个方法都可以给我们重载代码, 但要通过drop模似swing的路线走到importData是比较困难的, 而且importData方法是后面, 所以, 推荐优先尝试在importData里面实现我们要的东西 简单示例 1. 从JTextField拖曳内容给JLabel, TransferHandler实现 /** * add label drop action with transferhandler("text") * @author YS */ public class DragAndDropDemo4 { JFrame frame; Container container; JLabel lbl1; JLabel lbl2; JTextField txt1; JTextField txt2; JLabel lbl3; JTextArea txt3; public DragAndDropDemo4() { frame = new JFrame("DRAG AND DROP DEMO"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(null); container = frame.getContentPane(); frame.setBounds(100, 100, 400, 300); addComponent(); frame.setVisible(true); } private void addComponent() { lbl1 = new JLabel("text1"); lbl2 = new JLabel("text2"); txt1 = new JTextField(); txt2 = new JTextField(); lbl1.setBounds(10, 10, 40, 20); lbl2.setBounds(10, 40, 40, 20); txt1.setBounds(60, 10, 300, 20); txt2.setBounds(60, 40, 300, 20); txt1.setDragEnabled(true); txt2.setDragEnabled(true); container.add(lbl1); container.add(lbl2); container.add(txt1); container.add(txt2); addLabelDropEvent(lbl1); } private void addLabelDropEvent(JLabel lbl1) { lbl1.setTransferHandler(new TransferHandler("text")); } public static void main(String[] args){ SwingUtilities.invokeLater(new Runnable() { public void run() { new DragAndDropDemo4(); } }); } } 其实, 由于label一开始是没有transferhandler的, 所以只需要通过系统预设的transferhandler("text"), 与此同时生成了droptarget实例, 方便地实现了label的拖曳赋值 其系统机制: 当鼠标在label上released时, droptarget的drop方法被激活, 然后获取自身的transferhandler, 如果null, 则调用rejectDrop, 否则执行transferhandler.canImport判断这个控件是否能接受drop, 可以的话, 执行transferhandler.importData 2. 从JTextField拖曳内容给JLabel, DropTarget实现 /** * add label drop action with DropTarget * @author YS */ public class DragAndDropDemo5 { JFrame frame; Container container; JLabel lbl1; JLabel lbl2; JTextField txt1; JTextField txt2; JLabel lbl3; JTextArea txt3; public DragAndDropDemo5() { frame = new JFrame("DRAG AND DROP DEMO"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(null); container = frame.getContentPane(); frame.setBounds(100, 100, 400, 300); addComponent(); frame.setVisible(true); } private void addComponent() { lbl1 = new JLabel("text1"); lbl2 = new JLabel("text2"); txt1 = new JTextField(); txt2 = new JTextField(); lbl1.setBounds(10, 10, 40, 20); lbl2.setBounds(10, 40, 40, 20); txt1.setBounds(60, 10, 300, 20); txt2.setBounds(60, 40, 300, 20); txt1.setDragEnabled(true); txt2.setDragEnabled(true); container.add(lbl1); container.add(lbl2); container.add(txt1); container.add(txt2); addLabelDropEvent(lbl1); } private void addLabelDropEvent(JLabel lbl1) { lbl1.setDropTarget(new MyDropTargetAdapter()); } class MyDropTargetAdapter extends DropTarget{ @Override public void drop(DropTargetDropEvent e) { try { e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); Component com=e.getDropTargetContext().getComponent(); Transferable t=e.getTransferable(); String text=(String)t.getTransferData(DataFlavor.stringFlavor); if(com instanceof JLabel){ ((JLabel)com).setText(text); }else if(com instanceof JTextField){ ((JTextField)com).setText(text); } e.dropComplete(true); } catch (Exception ex) { e.rejectDrop(); ex.printStackTrace(); } } } public static void main(String[] args){ SwingUtilities.invokeLater(new Runnable() { public void run() { new DragAndDropDemo5(); } }); } } 由于DropTarget的drop已经实现我想要的, 并且没有像swing那样全面的检查并调用transferhandler的canImport和importData, 所以, 整个drop动作就到此结束 注意e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE), 如果没有这个, 就表示没有接受drop, swing就不会给你transable和dataflavor, 但在transferhandler的importData里, 在importData之前, 就swing就已经帮我们判断好acceptDrop还是rejectDrop, 结束后还有dropComplete 通过上面2种方法都可以实现drop动作, 但出于代码的严整性和程序的整体观念考虑, 还是推荐第一种, 在transferhandler的importData里面实现 to be continue... 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-03-08
分析的很好啊,最近也在学习 拖拽的应用。
|
|
返回顶楼 | |
发表时间:2010-08-28
拖拽确实比较头疼呀!~
|
|
返回顶楼 | |