`
netbaixc_gmail_com
  • 浏览: 55507 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java dnd拖拽实现分析纪要

阅读更多
Java dnd拖拽实现分析纪要

既有的Swing组件都内置了拖拽的支持,是怎么样支持呢?

首先,在Windows环境的jvm进程中,一个gui程序将启动两个线程:AWT-WINDOWS(AWT)和Event-Dispatch-Thread(EDT)。AWT-WINDOWS线程不断从windows操作系统中获取GUI事件并进行初步的底层处理;其中一些事件会被包装成高级的AWTEvent置入一个地方,而EDT线程的处理过程就包括不断在的适当时机从这个地方获取这些AWTEvent并进行高级处理。

然后,拖拽的效果就是由以下几个GUI操作事件及相应程序处理完成的。

1.       拖拽开始,拖拽结束。

2.       拖拽进入(源组件/目的组件),拖拽经过(源组件/目的组件),拖拽离开(源组件/目的组件),拖拽中鼠标的移动。

3.       拖拽操作式样变换。即随键盘操作,可以指示3种操作式样:剪切式,复制式,链接式。

这些事件发生后,AWT线程即会获取到事件通知并处理,底层处理后会包装交给EDT继续处理。而处理的程序逻辑一般设计为,对于鼠标图标,会被设计为随拖拽的起始及移动的整个过程中不同事件的发生而发生变化(比如在dragover事件中可能根据当时情况将图标变为一个叉表示不能拖入);同时,对于拖拽源组件和目的组件,随不同的事件通知也会被程序设计为产生不同的变化响应(比如,拖拽结束的事件处理中可能指令目的组件复制源组件的文字内容),从而最终实现了拖拽的效果。

最后,看一些JRE7中拖拽的实现类。

Swing的JComponent组件将一些功能委托给其成员ComponentUI代理。比如JTextField在构造方法中即会通过UIManager获得合适的TextUI实例(UIManager将根据当前look and feel设置获取)(此后JTextField的paint方法会调用该UI实例的update方法从而完成组件绘画),并调用该UI实例的installUI方法(在installUI中则包括给JTextField对象安装一些监听器,安装TransferHandler这个支持拖拽的关键成员,它包含DragHandler,DropHandler两个内部类,而这两个内部类是实现拖拽效果的核心逻辑,安装droptarget)。

Swing将ui类划分在几个包中,其中javax.swing.plaf中存放一些接口;javax.swing.plaf.basic中存放对接口的基本实现,即多种LAF的通用实现;javax.swing.plaf.metal中存放java默认LAF实现;另外还有javax.swing.plaf.multi用来实现多个LAF的综合效果实现;javax.swing.plaf.synth用来实现可通过配置xml文件更换颜色等皮肤的实现。

对于拖拽方面,BasicUI在installUI中一般会对组件安装mouseListener:

editor.addMouseListener(dragListener);

editor.addMouseMotionListener(dragListener);

该dragListener将监听发生在组件上的鼠标事件,当发现可能是新启动的拖拽鼠标动作并且组件dragEnable时,则立刻通知DragRecognitionSupport单例进行组件拖拽识别。该support单例将辨别鼠标动作本身,确认是组件拖拽开始,再通知组件的TransferHandler成员对象进行拖拽初始化,即,经其辨别headless环境和action支持后将初始化建立并委托调用TransferHandler.SwingDragGestureRecognizer的全局单例(成员包括全局单例dragsource及draghandler对象),该实例注册拖拽识别listener及设置sourceAction,最终将通知TransferHandler.DragHandler对象的dragGestureRecognized。在该方法中,将创建transferable及初始化autoscroll;并通过dragSource全局单例完成创建DragContext,并获取及初始化DragContextPeer全局单例(给该单例注册上该component作为拖拽 trigger,以供native code可以在处理底层事件时,可以通过x,y判定是否contains,从而缩小事件处理范围),并通知DragContextPeer单例拖拽开始,而DragContextPeer单例则会调用底层native code进行进一步的处理。

此后AWT线程将通过windows-api获取到系统底层的各种拖拽事件并进行底层处理,处理过程将会随时引用DragContextPeer单例(处理逻辑包括根据trigger过滤),并最终通知该单例合适的事件通知。Peer会将这些事件封装成合适的DragEvent并提交给EDT处理,提交后将促使AWT线程模拟等待EDT处理完该事件。EDT的处理逻辑是将事件交给拖拽开始时给组件创建的DragContext处理,而该context对象的处理则会调用其dragHandler成员的对应方法进行事件处理,以及给dragSource单例相应的监听通知,最后updateCurrentCursor等,最后EDT返回到AWT,peer处理返回给native code继续处理。

当拖拽开始后,鼠标图标在一个swing组件上游晃时,首先windows会对其顶层容器(如jwindows)视作拖拽源,经native code过滤后,如果该swing组件是此次拖拽trigger(源),则DragContextPeer能得到dragover的通知,进而进行后续处理;同时从另一个方面,这个也被视作一个拖拽目的,即AWT线程还会对每次拖拽启动建立一个DropContextPeer(为将来支持并发),并调用该peer的handle事件方法,该方法会将此底层事件包装后提交给EDT并促使AWT等待;EDT处理该事件时将track到该事件发生时的顶层组件,以及事件发生的坐标位置,由顶层容器组件的LightweightDispatcher进行初步处理。这个处理将由该事件产生新事件,其event source将从container(比如JFRAME)变为精确的jcomponent(比如JTextField),同时对于eventId=dragover的事件,有可能根据具体情况再增加dragExit,dragEnter两个事件(比如对jframe窗体是dragover,但鼠标实际是从一个jtextfield到另外一个jtextfield),这些精确的事件的处理会再次回到DropContextPeer中得到对应的process。这时的process会处理本身的一些成员数据(当前context,当前droptarget等),再将事件委托给source jcomponent的droptarget进行处理(如果已安装),此时的处理将是传递给对应组件的drophandler进行处理,同时会通知droptarget的注册监听,最后initializeAutoscrolling等。drophandler在处理过程中对event可以进行accept或reject,这两个动作会再去调用dropcontext进行处理,并最终转到peer处理成员数据。最后edt返回到AWT,peer handle处理结束返回native code当前drop action值,native code继续处理。

通过以上的分析,如果需要定制swing组件的拖拽逻辑,一个比较基础的入口是transferhandler;因为所有事件的处理都将经过其两个内部类逻辑处理(DragHandler和DropHandler);而swing包中的TransferHandler的具体实现,是这两个内部类的方法都把一些控制划分给了componet的一些属性设置或TransferHandler本身的回调方法,所以只需对组件设置合适的属性,或继承并override TransferHandler合适的方法并给此swing组件重新setTransferHandler,即可以定制新的逻辑。如果需要更深层次的定制,则需要细致考虑上述分析,选择合适的定制点。
JDK6中transferhandler几个关键方法的回调序列在此列一下:
getSourceActions(JComponent) — 当拖拽一开始时就会被调用,此后不再回调
createTransferable(JComponent) — 当拖拽一开始时就会被调用,此后不再回调
exportDone(JComponent, Transferable, int) — 当拖拽一结束就会被调用,不管目的组件有没有接受。
canImport(TransferHandler.TransferSupport) — 当拖拽的位置处在本组件可见范围内时将不断得到调用。
importData(TransferHandler.TransferSupport)-当拖拽结束在本组件可见范围时被调用一次。
TransferSupport有一个setDropAction(int action),对于目的组件,通过调用一次该方法,则可指示出在将要拖拽结束那一刻确认的action。如果该组件作为拖拽目的,并且在importData里返回了true,则源组件的transferhandler中的exportDone方法将最终得到该指定的action作为一个参数;如果该组件作为拖拽目的,但importData里返回了false,则exportDone方法将得到NONE。
特别注意,在importData方法里setDropAction是不会起作用的,这样做的理由的比较不易理解。
这样处理是因为swing认为从设计语义角度,拖拽结束的那一刻需要同时通知源组件和目的组件进行最后的处理;而就在那一刻,最终准备接受什么类型的dropAction已经得到了语义上的明确;尽管如此,Swing被要求在通知exportDone前应该再做一次处理,即确认拖拽到底有没有被目的组件接受,如果没有的话,这个确定的dropAction参数请swing自动处理为NONE后再传进来。以上的这些语义要求,其实也就限定了如果目的组件和源组件都是同一个AWT环境(比如一个JVM下的application)的swing组件,则源组件的transferhandler.exportData将等着目的组件transferhandler.importData执行结束得到其返回值经swing处理后再执行。
这样的一个隐含的执行顺序很容易让人想到是否可以在importData里setDropAction.但是在importData里再去调用setDropAction是违反语义的,因为dropAction的含义就是在importData执行前的那一刻确定下来的哪个action,而setDropAction这个方法本身的含义是设置在那一刻将会确定是哪个action,而在importData执行的时候那一刻已经过去了。
所以结论是在importData方法里setDropAction应该被禁止,应该在运行时抛出异常,可惜,截至到JDK6,也没有得到这样的支持。这应该算是一个设计BUG.







2
0
分享到:
评论
1 楼 mayi140611 2011-11-27  
高深,学习中,

相关推荐

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

    本文将详细介绍如何在React项目中使用React DND来实现拖拽排序。 首先,你需要安装React DND库。在你的项目根目录下,通过npm或yarn执行以下命令: ```bash npm install react-dnd react-dnd-html5-backend # 或者...

    学习使用ReactDnD实现嵌套列表的拖拽排序

    在这个主题中,我们将深入探讨如何利用ReactDnD来实现嵌套列表的拖拽排序。 首先,我们需要安装ReactDnD库。通过npm或yarn可以轻松完成: ```bash npm install react-dnd react-dnd-html5-backend # 或者 yarn add...

    Swing组件的DnD拖拽深度分析

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

    dnd.beta

    `java.awt.dnd`包提供了一系列丰富的API来支持拖放操作,包括拖动、放下以及相关的事件监听。通过实现这些API,我们可以轻松地为Java应用添加拖放支持,无论是对于文件还是简单的文本数据。 需要注意的是,尽管`...

    拖拽实现源代码DragPictrue

    在这个案例中,我们关注的是如何利用Java的DnD功能来实现图片的拖放效果。我们将深入探讨这个名为"DragPictrue"的程序,并分析其核心知识点。 首先,Java提供了`java.awt.dnd`包,它包含了处理拖放操作所需的所有类...

    Easyui扩展datagrid-dnd.js,实现拖动grd的必要文件

    Easyui的扩展datagrid-dnd.js,实现拖动datagrid行到其他数据表格的必要文件

    Web 前端拖拽实现实例分析

    本文将深入探讨如何在Web前端实现拖放功能,通过实例分析不同实现方式的优缺点,帮助开发者选择最适合项目需求的解决方案。 首先,让我们了解拖放的基本原理。拖放功能涉及两个主要事件:`dragstart`、`drag`、`...

    java拖动对象程序

    在这个"java拖动对象程序"中,我们将探讨如何利用Java Swing库来创建GUI以及实现拖放(Drag and Drop,DnD)功能。 首先,Java Swing库是Java AWT(Abstract Window Toolkit)的扩展,提供了丰富的组件和更现代的...

    reactDndDemo:react Dnd拖拽库的复杂应用实现

    React DnD(Drag and Drop)是React社区中一个强大的拖放库,它使得在React应用中实现拖放功能变得简单而优雅。本项目“reactDndDemo”旨在展示如何在实际项目中深入运用React DnD来创建复杂的拖放交互。通过分析此...

    Java鼠标拖拽功能.7z

    在Java编程中,实现鼠标拖拽功能是一项常见的需求,尤其在开发图形用户界面(GUI)应用时。这个压缩包文件“Java鼠标拖拽功能.7z”可能包含了一个或多个示例程序,演示了如何在Java中启用鼠标拖放操作。下面我们将...

    (java) JTree控件的拖拽,拖动

    本篇文章将深入探讨如何在`JTree`中实现拖拽(Drag and Drop,简称DnD)功能,使用户能够通过拖动节点来重新组织树形结构。 首先,为了启用`JTree`的拖放功能,我们需要在`JTree`实例上注册一个`DragSource`和`...

    React-dnd js库学习

    React-DND是一个流行的JavaScript库,专门用于在Web应用中实现拖放功能。它与React库无缝集成,使得在React组件中实现拖放操作变得简单易行。本教程将深入探讨React-DND的基本概念、安装、配置以及如何创建一个简单...

    Java 拖拽文件到文本框

    在这个场景中,标题"Java 拖拽文件到文本框"指的是实现用户能够通过拖放操作将本地文件直接放入文本框(JTextArea或JTextPane)的功能。这种功能在许多应用中都很有用,比如文件上传、代码编辑器等。 首先,为了...

    datagrid-dnd(可以拖放的datagrid)

    然而,通过启用drag and drop(拖放)功能,用户可以方便地将某一行数据拖动到新的位置,从而实现动态排序。这对于数据视图的个性化定制和数据分析时的临时排序非常有用。 实现datagrid-dnd的关键在于JavaScript...

    dnd txt文档阅读

    《DND TXT文档阅读:深度探索与高效利用》 DND(Dungeons & Dragons)是一种深受全球玩家喜爱的角色扮演游戏,其规则、设定等通常以文本形式记录,形成DND TXT文档。这些文档包含了丰富的游戏背景、角色设定、规则...

    Java中的Drag and Drop拖拽技术

    Java中的Drag and Drop拖拽技术是指在Java应用程序中,实现拖拽操作的技术。Drag and Drop技术可以将数据从一个组件拖拽到另一个组件中,实现数据的传输和交互操作。 Drag and Drop技术的实现可以分为两种方法:...

    react-dnd-scrollzone:用于可拖动项目的平滑滚动容器

    react-dnd-scrollzone 跨浏览器兼容的滚动容器,用于拖放交互。 import React , { Component } from 'react' ; import { DragDropContextProvider } from 'react-dnd' ; import ...

    DragText可拖动文本

    通过分析和学习这些代码,开发者可以深入理解Java拖放和剪贴板操作的机制,并在自己的项目中实现类似的功能。 总的来说,"DragText可拖动文本"是一个实用的Java应用,展示了如何利用Java的拖放API和剪贴板机制来...

    Java实现鼠标拖放功能的方法

    在Java中,这一功能主要由`java.awt.dnd`和`java.awt.datatransfer`包提供支持。下面我们将深入探讨如何在Java中实现这一功能。 首先,要实现拖放功能,我们需要理解两个关键概念:拖拽源(Drag Source)和放置目标...

    Java鼠标拖放功能的实现源码

    Java鼠标拖放功能的实现源码,之前一个Java例子是使用系统剪贴板来交换程序间的数据,本例将利用鼠标的拖放来交换程序的数据,即所谓的鼠标拖放功能。Java 提供了java.awt.dnd 和java.awt.datatransfer 包来支持该...

Global site tag (gtag.js) - Google Analytics