`

[DnD]Swing Drag And Drop 分析笔记

阅读更多
DnD(Drag And Drop)对于程序的用户友好度至关重要

常见的, 把一个文件直接拖入程序里, 把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...
分享到:
评论
2 楼 zouyazhou 2010-08-28  
拖拽确实比较头疼呀!~
1 楼 gml520 2010-03-08  
分析的很好啊,最近也在学习 拖拽的应用。

相关推荐

    Java中的Drag and Drop拖拽技术

    在Java中,实现Drag and Drop技术需要了解java.awt.datatransfer和javax.awt.dnd包中的类和接口,例如Transferable、DragSource、DropTarget、DragGestureRecognizer等。同时,需要了解JComponent的dragEnabled属性...

    java swing控件的drag和drop的实现方法

    在Swing中,实现拖放(Drag and Drop,DnD)功能可以帮助用户通过直观的方式来移动数据或对象,提升用户体验。本篇将深入探讨如何在Java Swing控件中实现拖放操作。 一、Java Swing中的Drag和Drop机制 1. **导入...

    draganddrop拖放库vuedndmobile

    Vue DnD Mobile,全称为"Vue Drag and Drop Mobile",是专门为Vue.js开发的一个拖放库,专为移动设备优化,使得在触摸屏上的交互变得更加自然和流畅。 拖放(Drag and Drop,简称DnD)是一种常见的用户交互模式,...

    eclipse rcp drag&drop

    5. **Drag and Drop Operations**: 拖放操作有多种类型,如`DND.DROP_COPY`(复制)、`DND.DROP_MOVE`(移动)和`DND.DROP_DEFAULT`(默认)。在`DragSourceAdapter`和`DropTargetAdapter`中,你可以根据需要重写...

    swing之拖拽功能DragDemo

    在Java的Swing库中,拖放(Drag and Drop,简称DnD)功能是一种用户友好的交互方式,允许用户通过鼠标操作将一个组件中的数据拖放到另一个组件上。本教程将聚焦于“swing之拖拽功能DragDemo”,探讨如何在Swing应用...

    Swing组件的DnD拖拽深度分析

    Swing组件的拖放(Drag and Drop, DnD)功能是Java GUI编程中的一个重要特性,它允许用户通过直观的拖动操作在组件之间传递数据。深入理解Swing的DnD机制对于创建用户友好、交互性强的应用程序至关重要。本文将详细...

    Drag_Drop_OLE实现

    7. **DnD效果**:`DoDragDrop`函数启动实际的拖放操作,可以设置参数来控制拖放过程中的视觉效果,如DRAGDROP_E_AFFECTED_NONE, DRAGDROP_S_DROP等。 通过以上知识点,我们可以实现一个MFC应用程序,允许用户在编辑...

    Beautiful-and-Accessible-Drag-and-Drop-with-react-beautiful-dnd-notes

    # Beautiful-and-Accessible-Drag-and-Drop-with-react-beautiful-dnd-notes 讲师 描述 拖放 (dnd) 体验通常用于对内容列表进行垂直和水平排序。 react-beautiful-dnd 是这些用例的绝佳工具。 它利用渲染道具模式...

    JQuery-tableDnD 拖拽的基本使用 Table Drag and Drop JQuery plugin

    在压缩包中的`Drag-Drop-Table-Plugin-with-jQuery-TableDnD`文件夹中,包含了完整的示例代码,包括HTML、CSS和JavaScript,供你参考和学习。 总之,jQuery TableDnD是一个强大且易于使用的插件,能帮助开发者轻松...

    Preventing inadvertant drag and drop(3KB)

    6. **使用现成的库和框架**:推荐一些开源库或框架,如jQuery UI、React DnD等,它们提供了一套完整的拖放解决方案,并内置了防止误操作的机制。 7. **最佳实践和注意事项**:提供在设计UI时应遵循的准则,以确保...

    react-beautiful-dnd-drag-and-drop-app

    这个名为"react-beautiful-dnd-drag-and-drop-app"的项目,是一个基于React和RBDND的示例应用,旨在展示如何在实际项目中集成和使用这一强大的库。 ### React简介 React是由Facebook开发的开源JavaScript库,用于...

    DragAndDrop:HTML5 DnD 的快速示例

    HTML5的拖放(Drag and Drop,简称DnD)功能是现代网页开发中的一个重要特性,它允许用户通过直观的拖放操作来处理元素。在本示例中,我们将深入探讨如何利用JavaScript实现HTML5的拖放功能。 一、HTML5拖放API概述...

    Dojo之路:如何利用Dojo实现Drag and Drop效果

    ### Dojo之路:如何利用Dojo实现Drag and Drop效果 #### 概述 随着Web开发技术的不断进步,用户界面的交互性和动态性也日益增强。Drag and Drop(拖放)作为一种直观的操作方式,在现代Web应用中变得越来越普遍。...

    drag-and-drop:拖放概念证明

    在IT行业中,拖放(Drag-and-Drop)是一种常见的用户界面交互技术,允许用户通过鼠标或其他输入设备将一个对象从一处“拖动”到另一处“放下”,以此来执行各种操作,如移动、复制、排序、关联等。这种直观且易于...

    react-drag-drop:适用于React用户的超级简单的拖放拖放库

    我只是觉得自己很难理解react-dnd和react-beautiful-dnd。 它们都具有出色的功能和支持,非常出色。 我需要一个库,该库可以让我快速进行拖放操作并进行自定义,如果有任何错误,我将不会花费太多时间来理解该库。 ...

    jquery-sample-drag-and-drop:通过拖放在jQuery中对元素进行排序,并使用Cookie记忆它们

    在这个“jquery-sample-drag-and-drop”项目中,我们关注的是jQuery的一个重要功能——拖放(Drag and Drop)操作,以及如何结合Cookie技术来持久化用户的排序设置。 拖放功能允许用户通过鼠标操作将元素从一处移动...

    dnd.beta

    在Java 2平台之前,拖放(Drag and Drop, DnD)功能的支持并不完善,特别是与本地窗口系统的交互方面存在缺失。现代用户界面设计中,允许用户从文件选择器将文件拖拽到应用程序内部已成为一种基本需求。为了满足这一...

    react项目使用react-dnd实现拖拽排序

    React DND(Drag and Drop)库是实现这一功能的利器。它允许开发者轻松地将拖放功能集成到React组件中,无需深入理解底层浏览器事件。本文将详细介绍如何在React项目中使用React DND来实现拖拽排序。 首先,你需要...

    react-beautiful-dnd-multi-list-typescript-example:React Beautiful Drag and Drop的多列表传输的打字稿示例

    多列表传输的打字稿示例。 是吗? ? 下载示例。 git clone git@github.com:abeaudoin2013/react-beautiful-dnd-multi-list-typescript-example.git ... 这是来自React Beautiful DnD的的链接。

    swing组件的详细介绍

    5. **DnD(Drag and Drop)功能**:允许用户在组件之间进行拖放操作。 6. **撤销/重做(Undo/Redo)支持**:增强了用户交互体验。 7. **继承层级**:Swing组件通常继承自`javax.swing.JComponent`,这个类继承自...

Global site tag (gtag.js) - Google Analytics