Wednesday, March 5, 2003

Drag & Drop With Java GUI Components

Abstract

The Java programming language is very powerful. In my opinion, it has brought the art of programming back to programmers by focusing on fundamentals such as data structures, reusable code, and application design. Though, like all programming languages, Java is not perfect. GUI development with Java can be frustrating at times. This is especially true in Window and Apple environments where Drag & Drop is pervasive. An application that does not properly participate in Drag & Drop can become quite frustrating to work with. Java applications are usually frustrating to use for this reason. The purpose of this paper is to tell my story about Java and Drag & Drop; to relay what I have learned and hopefully give readers of this post a jump start in their own development.

Disclaimer

This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.

Drag & Drop - My Experience

Where are the listeners?

For a developer, the first instinct for doing Drag & Drop would be to start looking at the listeners for the mouse, in fact looking at anything mouse related. This is futile though because, surprisingly, the implementation of Drag & Drop is not found in anything mouse related!

The place to start looking is the java.awt.dnd package. It is here that can be found all the tools needed to implement Drag & Drop. However the classes and interfaces here are quite confusing. Worse yet, documentation and examples are scarce as well.

The most confusing part of Drag & Drop in Java is that it needs to be developed by a programmer not simply implemented. Here is what that statement means. Programmers typically implement GUI events in Java by implementing a listener and assigning the listener to the object. Take JButton for example. An ActionListener is implemented and then assigned to a JButton instance using the addActionListener() method. When the button is pressed, the method implemented in the listener is run. Because listeners are used everywhere in the Java GUI APIs, it is natural to assume that Drag & Drop is also like this - Find the Drag & Drop listener, plug it in, and the work is done. Sadly this is not the case. A lot more development is needed to implement Drag & Drop.

Step by step

The first step in making any Java component able to do Drag & Drop is to create a new object that extends the existing one. This needs to be done because additions need to be made to the existing component. For example, create a new class, MyJTree that extend JTree.

Next, jump to the java.awt.dnd package. There are the 3 objects to start off with:

  • DragSource
  • DropTarget
  • DragGestureRecognizer

If I want to drag from MyJTree, then I need to add a private DragSource dragsource; to the MyJTree class. That is the simple explanation of what DragSource is for. If I want to drop onto MyJTree, then I need to add a private DropTarget droptarget; to the MyJTree class. That is the simple explanation of what DropTarget is for. Finally, for these two objects to be used, a DragGestureRecognizer needs to be used, so add a private DragGestureRecognizer dgr; to MyJTree.

Adding these three objects to MyJTree creates a new component that has the potential to:

  1. Recognize that a drag event has started by using DragGestureRecognizer
  2. Start dragging something by using DragSource
  3. Accept the drop of something by using DropTarget

But how should MyJTree respond when these events occur? Your developer instincts might be saying that the responses should be an implementation of some listener. This is absolutely correct! In fact, there are 3 listeners, one for each of the three objects just added to MyJTree:

  1. DragGestureListener
  2. DropTargetListener
  3. DragSourceListener

So, putting it all together, this is how drag & drop might be implemented:

Overload the default constructor for the MyJTree class. Create an instance of DragSource by calling its constructor. Using the DragSource object just created, call the createDefaultDragGestureRecognizer() method. This method requires an implementation of the DragGestureListener, which is one of the three listeners that need to be implemented. Also the Component c parameter of that method is the component that the recognizer is for, in other words the keyword this, MyJTree. Finally a DropTarget is constructed, calling its constructor, again using MyJTree (keyword this) for the Component parameter and an implementation of the DropTargetListener. It is a good idea to first make three blank listeners (have all the methods but the methods do not do anything), get everything set in MyJTree, then make sure everything compiles. Once that is done, move onto implementing the methods in the listeners so a Drag & Drop operation can be done.

By this time MyJTree has been developed with three objects from java.awt.dnd and those objects have been created (In MyJTree) utilizing one of their various overloaded constructors and blank implementations of their listeners. At this point implementing those listener methods is all that is left in creating a GUI component capable of performing Drag & Drop operations.

Walkthrough

Everything starts with the DragGestureListener.dragGestureRecognized() method. When a user starts to drag something on MyJTree the DragGestureListener hears it and fires the dragGestureRecognized() method.

NOTE At this time dragging has not begun yet.

The DragGestureListener has simply recognized that the user wants to start dragging. It calls dragGestureRecognized() to figure out what to do.

What the dragGestureRecognized() method needs to do is figure out whether or not a drag should begin, and if so, start it. Any code needed can be added to determine if a drag should start (Perhaps there are some nodes on MyJTree that should never be dragged). If a drag should start, dragGestureRecognized() needs to use its DragGestureEvent parameter and do something like:

Evnt.getDragSource().startDrag(evnt, someCursor, someTransferable, someDragSourceListener

The getDragSource() method will get the DragSource object added to MyJTree, and calling the startDrag() method will finally start a drag.

At this point, while dragging continues, the execution of Java code will wildly flip back and forth between the DragSourceListener and the DropTargetListener. The methods in these listeners are fairly self-explanatory. Here is a typical manner in which the methods are used:

  • DragSourceListener.dragOver() – Used to change the display of the mouse.
  • DropTargetListener.dragEnter() – Figures out if the target will accept a drop or not. If not, the Drag & Drop operation is canceled.
  • DropTargetListener.dragOver() – Changes the display of the object being dropped on (highlighting tree nodes for example).
  • DropTargetListener.drop() – Most important! Run when the mouse button is finally released and a user makes a drop.

DropTargetListener.drop() needs to do two things. One, at the end of the method it needs to use its DropTargetDropEvent parameter and call:

Evnt.getDropTargetContext().dropComplete(true);

This will end the Drag & Drop operation that began with DragGestureListener.dragGestureRecognized(). Two, before the drop method calls dropComplete(true) it needs to get what is being dragged and do something with it! Otherwise what is the point of dragging and dropping?

How does the drop() method know what was is being dropped? Looking at the DragGestureListener.dragGestureRecognized() method. At some point, the call:

Evnt.getDragSource().startDrag(evnt, someCursor, someTransferable, someDragSourceListener)

was made to start the drag. The object someTransferable is what is being dragged and can be any Java object as long as it, and all objects in it, implement the Transferable and Serializable interfaces. Think of the Transferable as an invisible connection between what was dragged from and what was dropped on.

At startDrag() a 100% complete !!copy!! is made of the object passed to the method as the Transferable. The fact that it is a !!copy!! is very important to know because in DropTargetListener.drop(), the Transferable gotten from the DropTargetDropEvent parameter does not reference the original object set as the Transferable. For example, if a TreeNode is dragged and that TreeNode implements Transferable and is set as the Transferable in startDrag() the TreeNode gotten in drop() is not simply a reference to the one on the source tree. It is a completely new object with the same information from the source tree copied exactly!! This is very important, especially when nodes need to be added/deleted from the tree. The original nodes in the tree need to be found from the copy in the Transferable.

Finally, in the drop() method, anything that needs to be done can be done to perform all the operations required for a successful drop, including contacting a database to perform inserts, updates, and deletions to tables.

Tip about Applets

Web browsers have a peculiar feature. A perfectly implemented Drag & Drop component will fail to do any Drag & Drop of any kind if the component is created in the Applet.start() method. When it is created here, for some reason the web browser itself will listen for Drag & Drop operations and all the code that has been developed for the Drag & Drop component will never be run. What is needed to overcome this problem is to create the GUI of the JApplet in a separate thread. This is not difficult to do. Simply move all the code that is in the Applet.start() method to a run() method, have the JApplet class implement Runnable, and in the Applet.start() method do something like:

Thread t = new Thread(this);
t.start();

This sounds very strange but it is the only way to have the code developed for the Drag & Drop component actually run without drag gestures being intercepted by the web browser.

No comments:

Post a Comment