Drag and Drop in the Eclipse UI

来源:百度文库 编辑:神马文学网 时间:2024/04/29 07:55:09
Copyright © 2003 International Business Machines Corp.  Eclipse Corner Article

Drag and Drop in the Eclipse UI
Summary
In this article, we discuss the drag and drop facilities provided by JFace andthe Eclipse platform UI. After reading this, you will know how to add drag anddrop support to your own Eclipse views, and how that support will interact withthe standard views in the Eclipse platform. Along the way, we‘ll also discussthat keyboard relative of drag and drop: cut and paste. You‘ll learn that puttingyour own custom objects on the clipboard is easy once you‘ve figured out thebasics of drag and drop. This article is intended to be read as a companionto theSWTdrag and drop article.
By John Arthorne, IBM OTI Labs
August 25, 2003
Doing the drag and drop
In keeping with the general philosophy of JFace, the JFace drag and dropadds a layer of functionality on top of the SWT drag and drop support. This layerallows the developer to deal directly with domain objects (such as resources,tasks, etc), without having to worry too much about the underlying windowcontrols. Rather than concealing or replacing the drag and drop support in SWT,the JFace drag and drop support works as an extension to the same conceptsfound in SWT drag and drop.
Transfer types
As you‘ll know from theSWT drag and drop article,the notion of transfer types is central to the drag and drop support in Eclipse-based UIs.To recap, transfer types allow drag sources to specify what kinds of object they allow to bedragged out of their widget, and they allow drop targets to specify what kinds ofobjects they are willing to receive. For each transfer type, there is a subclass oforg.eclipse.swt.dnd.Transfer. These subclasses implement the marshalingbehavior that converts between objects and bytes, allowing drag and droptransfers between applications. The following table summarizes the transfer typesprovided by the basic Eclipse platform, along with the object they are capable of transferring:
 
Transfer class Object it transfers
org.eclipse.swt.dnd.FileTransfer java.lang.String[] (list of absolute file paths)
org.eclipse.swt.dnd.RTFTransfer java.lang.String (may contain RTF formatting characters)
org.eclipse.swt.dnd.TextTransfer java.lang.String
org.eclipse.ui.part.MarkerTransfer org.eclipse.core.resources.IMarker[]
org.eclipse.ui.part.ResourceTransfer org.eclipse.core.resources.IResource[]
org.eclipse.ui.part.EditorInputTransfer org.eclipse.ui.part.EditorInputTransfer.EditorInputData[]
org.eclipse.ui.part.PluginTransfer org.eclipse.ui.part.PluginTransferData
The set of transfer types is open ended, because third partytool writers can implement their own transfer types for their domain objects.To implement your own transfer type, it is recommended that you subclassorg.eclipse.swt.dnd.ByteArrayTransfer.See theSWT drag and drop article for more informationon defining your own transfer types.
An important point about transfer types is that they don‘t necessarily need to storethe entire object as serialized bytes. In most cases it is simpler and moreefficient to just store enough information about where the object is found. Forexample, FileTransfer simply encodes a string which represents theabsolute path of the file in the file system. It does not store the entire file in thetransfer object.
Transfer types supported by the standard views
Many of the basic views you see in Eclipse already support varioustransfer types. It is important to understand what transfer types are supported byeach view, because this dictates how the drag and drop support in your view willinteract with other basic views found in the Eclipse platform UI.
The Navigator view supports dragging and dropping files (FileTransfer),and resources (ResourceTransfer). For example, you can drag a filefrom the Navigator view into Windows Explorer or the Windows Desktop. Similarly,you can import resources into Eclipse simply by dragging them from Windows intothe Navigator view of your workbench. You can also drag files between two instancesof Eclipse, or drag within a single Navigator to copy and move files withinyour workspace. If your view supports either FileTransfer orResourceTransfer, then users will be able to transfer resources betweenyour view and the Navigator view.
The Tasks and Bookmarks views support dragging of markers (MarkerTransfer).Dragging a selection of tasks from the Tasks view into an application such as MSWord will generate a textual marker report (TextTransfer). You can also drag markersout of the Tasks and Bookmarks views into other parts of the workbench, such asthe editor area. Dragging a marker to the editor area will open the associatedresource in the editor and jump to that marker location in the editor.
Finally, the editor area supports dropping of editor inputs (EditorInputTransfer),resources, or markers. Dragging these objects to the editor will cause it to locate andopen an appropriate editor for the given resource, editor input or marker. In the caseof markers, it will also jump to that marker location in the editor.
A running example: go go gadgets!
For the remainder of this article, we‘ll make use of a running example. This exampleis an Eclipse plug-in for manipulating a simple object model of gadgets. Gadgetsare simply named objects that can be formed into trees. Each gadget knows its parentand its children. The example includes two views, one containing a table viewer, andthe other containing a tree viewer. Drag and drop can be used to copy or movegadgets between these views, and to rearrange the order or hierarchy of gadgetswithin a view. There is a GadgetTransfer class for encoding an arrayof gadgets to and from a byte array. Complete source code for the example is foundhere.
Adding JFace viewer drag support
Adding drag support to a viewer means that it enables the user to selectany item in the viewer with the mouse, and drag it into another vieweror another application. Drag support can be added to any subclass oforg.eclipse.jface.viewers.StructuredViewerusing the addDragSupport(int, Transfer[], DragSourceListener)method. From our gadget example, here is the code for associating a drag listenerwith a tree viewer:
 
TreeViewer gadgetViewer = new TreeViewer(...);int ops = DND.DROP_COPY | DND.DROP_MOVE;Transfer[] transfers = new Transfer[] { GadgetTransfer.getInstance()};viewer.addDragSupport(ops, transfers, new GadgetDragListener(viewer));
GadgetDragListener is an implementation of the SWT interfaceorg.eclipse.swt.dnd.DragSourceListener. There is nothing specificto JFace about the implementation of DragSourceListener, so youcan learn more about its implementation in theSWT drag and drop article.
Adding viewer drop support
Drop support can be added to viewers using the StructuredViewer.addDropSupport(int,Transfer[], DropTargetListener) method. This allows your viewer tobe the target of a drop operation. The code for adding drop support is almost thesame as for adding drag support:
TreeViewer gadgetViewer = new TreeViewer(...);int ops = DND.DROP_COPY | DND.DROP_MOVE;Transfer[] transfers = new Transfer[] { GadgetTransfer.getInstance()};viewer.addDropSupport(ops, transfers, new GadgetTreeDropAdapter(viewer));
 
JFace provides a standard implementation of DropTargetListenercalled org.eclipse.jface.viewers.ViewerDropAdapter.This adapter makes it easy to add drop support for simple cases. If you have morecomplex requirements, you can always override the SWT DropTargetListenerinterface directly for ultimate flexibility.When sub-classing ViewerDropAdapter, simply implement its twoabstract methods: validateDrop(Object target, int operation, TransferData transferType),and performDrop(Object data).
validateDrop is called whenever the user moves over a new itemin your viewer, or changes the drop type with one of the modifier keys.The method provides the current drop target, operation, and transfer type.The return value of this method indicates whether a drop at the current location isvalid or not. A return value of false will change the drag icon to indicateto the user that it is illegal to drop what they are dragging at the currentlocation. In our gadget example, it is always legal to drop a gadget, so thevalidateDrop implementation simply looks like this: public boolean validateDrop(Object target, int op, TransferData type) {return GadgetTransfer.getInstance().isSupportedType(type);}
This code just makes sure that the user is indeed dropping a gadget, and notsome other object such as a resource or marker. If you have more complexvalidation requirements based on the target object, you can do that here. For example,in a file navigator, you may want to allow dropping on top of directories, but not on top offiles.
performDrop is called when the user lets go of the mouse button,indicating that they want the drop to occur. Your implementation shouldaccordingly perform the expected behavior for that drop. Context for thedrop is provided by the methods getCurrentTarget, getCurrentOperation,and getCurrentLocation on ViewerDropAdapter. Mostimportantly, at the time when performDrop is called, getCurrentTargetwill provide the object in your viewer that is currently under the mouse. Here is theperformDrop method from our gadget example:
public boolean performDrop(Object data) {1 Gadget target = (Gadget)getCurrentTarget();2 if (target == null)3 target = (Gadget)getViewer().getInput();4 Gadget[] toDrop = (Gadget[])data;5 TreeViewer viewer = (TreeViewer)getViewer();6 //cannot drop a gadget onto itself or a child7 for (int i = 0; i < toDrop.length; i++)8 if (toDrop[i].equals(target) || target.hasParent(toDrop[i]))9 return false;10 for (int i = 0; i < toDrop.length; i++) {11 toDrop[i].setParent(target);12 viewer.add(target, toDrop[i]);13 viewer.reveal(toDrop[i]);14 }15 return true;}
In lines 1-3, it is determining what the target gadget is. The target is the item thatis currently under the mouse when the drop occurs. If there is no item under the mouse, ittakes the viewer‘s input element as the target. On lines 7-9, it is making surethat the user is not dropping an item onto itself, or onto a child of itself. You maybe wondering why this validation was not done in the validateDropmethod. In SWT, the transfer of data from the source to the target is done lazilywhen the drop is initiated. So, as the user is dragging, the destination has noway of finding out what source object is being dragged until the drop is performed.Returning false from the performDrop method willcancel the drop. On lines 10-13, it is performing the actual drop. This involves updatingthe gadget with its new parent (line 11), and then adding and revealing the new itemin the viewer (lines 12-13). Finally, the method returns true on line 15 to indicatethat the drop was successful.
ViewerDropAdapter has two more interesting methods for controllingdrag feedback effects. The method setFeedbackEnabled is used to turninsertion feedback on or off. If insertion feedback is on, the cursor will change whenthe mouse is hovering between two items to indicate where the insertion will occur.Using this effect, you can allow the user to sort items in a tree using drag and drop.In your drop adapter, use the method getCurrentLocation to determineif the cursor is currently directly on, before, or after the current target object. Hereis how the gadget drop example can be updated to make use of this locationinformation:
public boolean performDrop(Object data) {//set the target gadget according to current cursor locationGadget target = (Gadget)getCurrentTarget();if (target != null) {int loc = getCurrentLocation();if (loc == LOCATION_BEFORE || loc == LOCATION_AFTER)target = target.getParent();}if (target == null)target = (Gadget)getViewer().getInput();Gadget[] toDrop = (Gadget[])data;TreeViewer viewer = (TreeViewer)getViewer();// ... remainder of method is the same as previous example ...
In this snippet, it looks at the cursor location to see if it is before or after anitem in the tree. When a gadget is dropped between other gadgets, it sets theparent to be the parent of the neighboring gadget. Said another way, thedropped gadget will become a sibling of the gadget it is dropped next to.
The method ViewerDropAdapter.setScrollExpandEnabled is used toturn scroll and expansion effects on or off. When this is turned on, hoveringnear the bottom of a tree or table will cause the widget to automatically scroll inthat direction. Hovering over a collapsed tree item for a sufficient amount of timewill cause the item to be expanded. In most cases you should leave these effectson, otherwise users will not able to use drag and drop effectively when your treeor table contains many items.
Plugin drop handling
Due to the UI layering imposed by the plug-in mechanism, viewers are oftennot aware of the content and nature of other viewers. This can make dragand drop operations between plug-ins difficult. For example, our gadgetplug-in may want to allow the user to drop gadget objects into the Navigatorview. Since the Navigator view doesn‘t know anything about gadget objects(the Navigator only displays org.eclipse.core.resources.IResourceobjects), it would not be able to support this. Similarly, another plug-in may wantto drop some other kind of objects into the views from the gadget example.To address this problem, a plug-in drop support mechanismis provided by the workbench. This mechanism essentially delegates thedrop behavior back to the originator of the drag operation. In the gadget example,we use this extension point to drop gadgets into the Navigator view, and createfiles containing descriptions of the gadgets that were dropped. Here are thesteps required to add drag and drop behavior using this mechanism:
Step 1) In your plugin.xml, define an extension on the "org.eclipse.ui.dropActions"extension point. Here is an example XML declaration:

Step 2) Implement the code that will perform the drop. This work is done bythe class defined in the extension markup above, which must implementorg.eclipse.ui.part.IDropActionDelegate.This interface defines a single run() method that gets called when thedrop occurs. The run method is supplied with the object being dragged, as well as theobject under the cursor when the drop occurs. Here is an example implementation ofthe drop delegate from the gadget example:
public boolean run(Object source, Object target) {1 if (target instanceof IContainer) {2 GadgetTransfer transfer = GadgetTransfer.getInstance();3 Gadget[] gadgets = transfer.fromByteArray((byte[])source);4 IContainer parent = (IContainer)target;5 for (int i = 0; i < gadgets.length; i++) {6 writeGadgetFile(parent, gadgets[i]);7 }8 return true;9 }10 //drop was not successful so return false11 return false;}
On line 1, it ensures that the gadget is being dropped on some kind of container.In practice, your drop delegate may support being dropped on several differenttypes of objects, with different behavior for each type. On lines 2-3, it makes useof a convenience method to extract the transferred gadgets from the byte array in thetransfer data. Since the plug-in transfer data contains a byte array, this is the exactsame code that typically exists in your custom Transfer subclass.It then iterates over the transferred gadgets, and creates a file in the target containerfor that gadget. Finally, it returns true if the drop was successful (line 8), or false if the dropoccurred on a target object that was not supported (line 11).
Step 3) In the viewer that will be the source of the drag and drop,add drag support using the StructuredViewer.addDragSupport() methoddescribed earlier. In the array of supported transfer types, include thesingleton instance of org.eclipse.ui.part.PluginTransfer. In yourimplementation of DragSourceListener, the dragSetDatamethod must set the data to be an instance of org.eclipse.ui.part.PluginTransferData.This object consists of the id of your drop action,along with the data being transferred. Here is the code in the drag listener from thegadget example:
public void dragSetData(DragSourceEvent event) {1 IStructuredSelection selection = (IStructuredSelection)viewer.getSelection();2 Gadget[] gadgets = (Gadget[])selection.toList().toArray(new Gadget[selection.size()]);3 if (GadgetTransfer.getInstance().isSupportedType(event.dataType)) {4 event.data = gadgets;5 } else if (PluginTransfer.getInstance().isSupportedType(event.dataType)) {6 byte[] data = GadgetTransfer.getInstance().toByteArray(gadgets);7 event.data = new PluginTransferData("org.eclipse.ui.examples.gdt.gadgetDrop", data);8 }9 }}
Lines 1-2 are the familiar code for creating an array of gadgets from the viewer‘sselection. Line 4 handles the old case of a gadget transfer by simply setting theevent data to be the correct type for GadgetTransfer. Lines 6-7are the interesting new code for handling a plug-in drag event. First, it uses a conveniencemethod on the GadgetTransfer class for converting the array of gadgetsinto a byte array. Next, it creates a PluginTransferData object, passingin the id of the plug-in drop action that was declared in the plugin.xml in step 1), alongwith the serialized gadget array.
Step 4) In the viewer that will receive the drop, the drop listenerfor that viewer must subclass org.eclipse.ui.part.PluginDropAdapter,which in turn subclasses ViewerDropAdapter as described earlier.Be sure to invoke the super methods for validateDrop and performDrop incases where your adapter does not understand the transfer type. Followingfrom our earlier example, the viewer’s declaration would looklike this:
TreeViewer gadgetViewer = new TreeViewer(...);int ops = DND.DROP_COPY | DND.DROP_MOVE;Transfer[] transfers = new Transfer[] {GadgetTransfer.getInstance(), PluginTransfer.getInstance()};viewer.addDropSupport(ops, transfers, new GadgetTreeDropAdapter(viewer));
The basic Workbench views such as the Navigator viewalready have this support added. It is recommended that anyone definingtheir own views should add the plug-in support, in anticipation of futurethird party plug-ins wanting to drop content into their views. For more informationabout this advanced drag and drop mechanism, refer to the documentationfor the org.eclipse.ui.dropActions extension point.
Cut, copy and paste
Cut and paste can be thought of as the keyboard equivalent of drag and drop. Once you‘vemastered drag and drop support, you‘ll find that cut and paste is a snap. Onceagain, the gadgets example provides a complete implementation of cut and pastesupport. Here is code for adding cut and paste support from within an Eclipse view(this code goes in the view‘s createPartControl method):
public void createPartControl(Composite parent) {viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);//... initialize viewer‘s content and label providers ...clipboard = new Clipboard(getSite().getShell().getDisplay());IActionBars bars = getViewSite().getActionBars();bars.setGlobalActionHandler(IWorkbenchActionConstants.CUT,new CutGadgetAction(viewer, clipboard));bars.setGlobalActionHandler(IWorkbenchActionConstants.COPY,new CopyGadgetAction(viewer, clipboard));bars.setGlobalActionHandler(IWorkbenchActionConstants.PASTE,new PasteTreeGadgetAction(viewer, clipboard));}
This code simply creates a new SWT clipboard, and then defines global actionsfor cut, copy, and paste using that clipboard. The IActionBarsinterface is used for hooking into global actions. Note: SWT clipboardobjects are operating system resources that must be disposed when no longerneeded. In the gadget example, we dispose() the clipboardwhen the view is disposed. Disposing of an SWT clipboard instance does not removethe data from the operating system‘s clipboard.
The actions that are provided as global action handlers should be subclasses of theorg.eclipse.jface.action.Action class. The code for these actions issimilar to the drag and drop code, except that they use the clipboard as the transfermechanism, rather than the drag and drop event handlers. Here is the code for therun method of the cut action:
public void run() {1 IStructuredSelection selection = (IStructuredSelection)viewer.getSelection();2 Gadget[] gadgets = (Gadget[])selection.toList().toArray(new Gadget[selection.size()]);3 clipboard.setContents(4 new Object[] { gadgets },5 new Transfer[] { GadgetTransfer.getInstance()});6 for (int i = 0; i < gadgets.length; i++) {7 gadgets[i].setParent(null);8 }9 viewer.refresh();}
On lines 3-5, the gadgets are placed on the clipboard, along with the Transfer object that will be used for serializing them. Lines 6-8 removethe gadgets from the source view (since they are being cut, not copied), and line 9refreshes the view to update it with the new contents. The copy action is almostidentical, except lines 6-9 are removed, since we don‘t want to remove the gadgetsfrom the source view on copy. The paste action for pasting into a tree looks like this:
public void run() {1 IStructuredSelection sel = (IStructuredSelection)viewer.getSelection();2 Gadget parent = (Gadget)sel.getFirstElement();3 if (parent == null)4 parent = (Gadget)viewer.getInput();5 Gadget[] gadgets = (Gadget[])clipboard.getContents(GadgetTransfer.getInstance());6 if (gadgets == null)7 return;8 //cannot drop a gadget onto itself or a child9 for (int i = 0; i < gadgets.length; i++)10 if (gadgets[i].equals(parent) || parent.hasParent(gadgets[i]))11 return;12 for (int i = 0; i < gadgets.length; i++) {13 gadgets[i].setParent(parent);14 }15 viewer.refresh();}
Lines 1-4 compute the parent gadget for the gadgets that are about to be pasted.If there is a gadget currently selected, it will become the new parent, otherwise the rootgadget is used. Line 5 is the crucial step that takes the gadget objects off the clipboard.You should always check that the returned value is not null, since there may not bean object of the requested type on the clipboard. Lines 9-15 are the familiar codethat we had in the drop event handler, first ensuring that we‘re not dropping a gadgetonto itself or a child of itself, and then setting the parent elements for the droppedgadgets.
In your implementation, you will probably want to factor out the paste and dropcode into a common place, since they both do the same thing. Likewise, the code forthe cut action is similar to the code in dragFinished in the drag actionhandler. For clarity, the gadget example duplicates the code in both places, but you‘llmake your code easier to maintain if you keep it all in one place.
Summary and further information
You now have all the information you need for adding drag/drop and cut/paste supportin your own JFace viewers. You should also know all about the standard transfer typessupplied by the platform, and what transfers are supported by the standard viewsin the platform.
For more information, browse through the complete source from thegadgets example. The UI readmeexample, available from the eclipse.orgdownloads page,also implements some drag and drop support. For more in depth examples, youcan look at the drag and drop support implemented within the UI plug-in itself. Forexample, see org.eclipse.ui.views.navigator.ResourceNavigator tosee how the Navigator view implements its drag and drop behavior. The Navigatoruses two helper classes, NavigatorDragAdapter andNavigatorDropAdapter, which are also instructive to look at.
Acknowledgements
Thanks to Knut Radloff from the Eclipse Platform UI Team and Jim des Rivièresat OTI Labs for proof reading and providing feedback for this article.
Java and all Java-based trademarks and logos are trademarks or registered trademarks of SunMicrosystems, Inc. in the United States, other countries, or both.