Java dnd拖拽实现分析纪要

 

Java dnd拖拽实现分析纪要

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

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

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

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

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

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

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

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

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

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

对于拖拽方面,BasicUIinstallUI中一般会对组件安装mouseListener

editor.addMouseListener(dragListener);

editor.addMouseMotionListener(dragListener);

dragListener将监听发生在组件上的鼠标事件,当发现可能是新启动的拖拽鼠标动作并且组件dragEnable时,则立刻通知DragRecognitionSupport单例进行组件拖拽识别。该support单例将辨别鼠标动作本身,确认是组件拖拽开始,再通知组件的TransferHandler成员对象进行拖拽初始化,即经其辨别headless环境和action支持后将初始化建立并委托调用TransferHandler.SwingDragGestureRecognizer的全局单例(成员包括全局单例dragsourcedraghandler对象),该实例注册拖拽识别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(为将来支持并发),并调用该peerhandle事件方法,该方法会将此底层事件包装后提交给EDT并促使AWT等待;EDT处理该事件时将track到该事件发生时的顶层组件,以及事件发生的坐标位置,由顶层容器组件的LightweightDispatcher进行初步处理。这个处理将由该事件产生新事件,其event source将从container(比如JFRAME)变为精确的jcomponent(比如JTextField),同时对于eventId=dragover的事件,有可能根据具体情况再增加dragExitdragEnter两个事件(比如对jframe窗体是dragover,但鼠标实际是从一个jtextfield到另外一个jtextfield),这些精确的事件的处理会再次回到DropContextPeer中得到对应的process。这时的process会处理本身的一些成员数据(当前context,当前droptarget等),再将事件委托给source jcomponentdroptarget进行处理(如果已安装),此时的处理将是传递给对应组件的drophandler进行处理,同时会通知droptarget的注册监听,最后initializeAutoscrolling等。drophandler在处理过程中对event可以进行acceptreject,这两个动作会再去调用dropcontext进行处理,并最终转到peer处理成员数据。最后edt返回到AWT,peer handle处理结束返回native code当前drop action值,native code继续处理。

通过以上的分析,如果需要定制swing组件的拖拽逻辑,一个比较基础的入口是transferhandler;因为所有事件的处理都将经过其两个内部类逻辑处理(DragHandlerDropHandler);而swing包中的TransferHandler的具体实现,是这两个内部类的方法都把一些控制划分给了componet的一些属性设置或TransferHandler本身的回调方法,所以只需对组件设置合适的属性,或继承并override TransferHandler合适的方法并给此swing组件重新setTransferHandler,即可以定制新的逻辑。如果需要更深层次的定制,则需要细致考虑上述分析,选择合适的定制点。

你可能感兴趣的:(Java dnd拖拽实现分析纪要)