最近学习Eclipse,发现当前市场上的教程对SWT控件的拖放都说得词不达意,无法理解,包括质量较高的《Java开发利器:Eclipse SWT/JFace 核心应用》。自己花了很多时间研究才搞明白,心得如下:
SWT中有关拖放操作的类都在org.eclipse.swt.dnd包中。实现拖放首先需要一个DragSoruce类,也就是被拖放的对象,一个DropTarget,也就是要放的目的地对象,还要在两者之间传输拖放对象携带的数据,用Transfar类来定义。在拖的过程中将Java的数据转化为本地保存的全局变量数据,然后在放的过程中,再从本地保存的全局变量中将数据取出。
拖放源是一个类,真正拖放的是在创建时要绑定的控件,例如
Table t= new Table(shell,SWT.NONE) DragSource source=new DragSource(t,DND.Drop_MOVE|DND>DROP_COPY) |
同一widget只能绑定在一个DragSource或DropTarget上。创建拖放源的第二个参数是拖放过程中支持的操作类型,它决定了拖到目标上。DND类中定义了专用于拖放的一些常量。
操作常量 |
说明 |
DROP_COPY |
The item is copied when dragged in or out of this control. |
DROP_MOVE |
The item is moved from its current location to wherever it’s dropped. |
DROP_LINK |
Dropping the item creates a link back to the original. |
DROP_NONE |
Nothing happens when the item is dropped. |
上例中表示允许移动和复制。
拖放源创建后要定义拖放过程中需传递的数据类型,即将什么样的数据格式存放在本地系统的全局变量中;使用DragSource.setTransfer(Transfer[] transferAgents)方法完成数据的定义,其参数是一个Transfer实例(一般是其子类的实例)构成的数组例如:
dragSource.setTransfer(new Transfer[] { TextTransfer.getInstance() });//只传递字符串
或允许传递两种数据类型:
Source.setTransfer(new Transfer[] { TextTransfer.getInstance(), FileTransfer.getInstance()});
最后要添加拖放事件处理方法,即:dragSource.addDragListener(new DragSourceListener)或使用DragSourceAdapter。该监听器包含了3个事件:
a) dragStart(DragSourceEvent):开始拖动一瞬间发生;一般用于判断是否开始拖动。如不允许拖动则令event的doit属性为false。例如以表格为拖放源时要判断表格有选中的行时才能开始拖动。
b) dragSetData(DragSourceEvent):释放的操作已执行时触发。该方法须设置要drop的数据,起始就是给event的data域赋值。event的dataType域规定了要设置的数据类型,如果拖放源在创建时绑定Transfer时允许传递多种数据类型(也就是绑定了多种Transfer),则需要在这里根据event.dataType来确定将什么样格式的数据放入data域中,例如:
if (TextTransfer.getInstance().isSupportedType(event.dataType)) {…//给event.data赋值字符串} else if (FileTransfer.getInstance().isSupportedType(event.dataType)) {…//给event.data赋值字符串数组} |
c) dragDinished(DragSourceEvent):完成拖放后的事件,一般是一些清理工作,例如如果拖放被定义成移动,在要在拖动源中删除已移动的数据。通过event.detail域可以得知目标对象进行了什么操作,这些操作包括
i. DND.DROP_COPY: 目标对象复制了数据
ii. DND.DROP_MOVE: 目标对象剪切了数据,需要删除原来的数据
iii. DND.DROP_LINK: 目的对象创建了快捷方式
iv. DND.DROP_TARGED_MOVE:目的对象改变了数据的位置,通常用于移动文件
不同的操作对应不同的鼠标形状。拖放时到底实现的是什么操作,需要在目标控件的监听器中确定(见后)。
SWT使用Transfer类实现拖动源到释放目的地在数据类型上的沟通:移动的数据必须是双方都可理解的格式。Transfer存在的理由是需要将拖放的数据保存在本地操作系统中作为全局变量,因此需要有一个实现java数据到本地数据格式之间转换的机制。
Transfer的每一个子类都代表特定的数据类型,而且知道如何将数据在java和本地操作系统之间转换,包括文本、文件和RTF格式的文本,如果这些数据类型不满足要求,用户还可以自定义Transfer的子类(参考org.eclipse.swt.dnd.Transfer 和org.eclipse.swt.dnd.ByteArrayTransfe的文档)。就应用而言可以把Transfer实例看成一个代表特定数据类型的黑箱。每个子类都有一个静态工厂方法getInstance()来创建一个实例,我们只需将它作为参数来指定要传递的数据类型,无须调用它的其他方法。实质上,通过Transfer,SWT调用javaToNative()和nativeToJava()来转换数据(在需要时)。通用的Transfer子类有:
l FileTransfer:名义上用于传递一个或多个文件,实际上是字符串数组(可理解为多个文件路径储存在字符串数组中);
l TextTransfer:传递字符串;
l RTFTransfer:传统rich text formatting格式的字符串
通过创建一个DropTarget来注册一个控件使其具有接收拖放的能力,例如:
DropTarget target = new DropTarget(table, DND.DROP_COPY | DND.DROP_DEFAULT);
其中第二个参数定义释放目标允许的操作。在鼠标拖动的过程中,我们可以通过鼠标图形的变化来得知当前经过的是否是一个有效的DropTarget,这个过程叫做“drag over effect”。鼠标图形同时也可以告诉我们在数据被Drop之后,什么样的操作会被执行,是拷贝还是移动还是别的什么。之后需要通过setTransfer方法设定允许接收的数据类型。一个示例:
int operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK; Transfer[] types = new Transfer[] {TextTransfer.getInstance()};//和源一样可定义多种Tranfer DropTarget target = new DropTarget(label, operations); target.setTransfer(types); |
最后需要为Target添加监听器:addDropListener(new DorpTargetListener()); 监听器的所有方法的参数都是DropTargtEvent,它有几个重要的属性:
a) currentDataType:将要释放的数据的类型。它是一个TransferData类的实例,我们无需知道其具体值,因为这种类是给Transfer判断是非支持用的:Transfer.isSupportedType(transferData).
b) dataTypes: DragSource可提供的数据类型的集合(TransferData类的集合)
c) operations:可提供的操作的集合,是源和目标允许操作的交集;
d) detail:拖放完成时将被执行的操作
e) data:存放拖放的数据
监听器包含的方法有:
l dragEnter(DropTargtEvent event): 光标进入目标对象区域时触发. 此方法一般用来定义拖放的操作类型,即给event.detail赋值; event.detail的值确定了最终拖放到底执行什么操作。
event.detail的值只能是event.operations中的某一个, 或者是DND.DROP_NONE(也就是不产生任何操作)。而event.operations则是拖放源DragSource支持的所有操作(创建时输入的第二个参数)与目的DropTarget支持的所有操作的交集。(这部分是所有教材都未提到的,包括一些国外教材如《SWT/JFace in Action》也没说清楚)它本身是一个整数,通过观察其二进制数的各位是否为1可知道包含了哪些操作(可通过按位与运算获知)。
DropTarget在创建时可增加DND.DROP_DEFAULT参数,表示允许定义默认操作,但它具体指哪个操作需要在本方法中定义,也就是给DND.DROP_DEFAULT指定一个具体的操作。如果在本方法中和在dragOperationChanged()中都没给它定义,则它会被系统定义为DND.DROP_MOVE。DND.DROP_DEFAULT的目的就是为了判断拖动的过程中如果有没有辅助按键被按下,没有的话event.detail就等于DND.DROP_DEFAULT,这时就可指定event.detail来实现默认操作。例如:
if(event.detail==DND.DROP_DEFAULT){ //给event.detail赋的值必须是event.operations中的一个,event.operations中的操作都是DragSource所支持的. if((event.operations&DND.DROP_COPY)!=0){ event.detail=DND.DROP_COPY; }else{ event.detail=DND.DROP_NONE; } } |
拖放中如果按了辅助键,自然就不再是默认操作了,辅助键产生的影响在方法dragOperationChanged()中定义。
l dragOver(DropTargtEvent event): 光标进入目标对象区域中的事件,只要光标在区域中,该事件会不停触发,即使光标不动。通常在对表格或树拖动时要用到这个方法,和event.feedback属性
l dragOperationChanged(DropTargtEvent event):用户按下或放开辅助键时触发的事件,辅助键的定义根据本地系统而定;但辅助键定义的事件必须是包含在event.operations中的,否则无效(event.detail变成DND.DROP_NONE)。在operations允许的前提下:
n 拖放时按住Ctrl,event.detail变成DND.DROP_COPY;
n 按住Shift,event.detail变成DND.DROP_MOVE;
n 同时按住CTRL+SHIFT,event.detail变成DND.DROP_LINK;
n 放开辅助键则event.detail变成DND.DROP_DEFAULT;
如果没有在DropTarget添加DND.DROP_DEFAULT,则放开辅助键event.detail变成DND.DROP_MOVE。
在这里还可以改变currentDataType的值(但改后的值必须是dataTypes中的一个)。
l dragLeave:鼠标离开对象时触发的事件(或拖放时按下ESC键)。一般用于拖放完成后释放一些资源
l dropAccept:在完成拖放之前事件提供了最后一次定义数据类型的机会
l drop:拖放完成后触发的事件。通过evet.data可获得由拖放源存放到全局变量中的数据。例如
if (TextTransfer.getInstance().isSupportedType(event.currentDataType)) {…//读取event.data } else if (FileTransfer.getInstance().isSupportedType(event. currentDataType)) {…//读取event.data } |