Dragging and dropping panels in Java 5: Tutorial
Severely created by Bryan Smith on Friday May 16 2008.
Please share. You may (and should!) copy, modify and distribute, but attribute. Email me with questions.
Url attack: My Blogger Home My Portfolio bryanesmith@gmail.com
Brief introduction
This tutorial offers source code (with screenshots) for a demo application that allows a user to drag and drop panels, with the result of reordering the panels. The code will compile and run in Java 5.
The information in this tutorial can easily be adapted by a developer to drag and drop any type of GUI object, as well as produce other effects besides reordering GUI elements. For simplicity, I'm sticking to this one scenario application.
The tutorial are may contain typos. The code, however, will compile and run.
Contents
There are a plethora of resources for developers wishing to implement drag and drop (endearingly abbreviated DnD) for Java 6, including the Sun DnD Tutorial (Java 6).
However, there is only a small spattering of resources if you wish to implement drag and drop in Java 5, and I failed to find a single tutorial that made the task as trivial as I hoped it would be.
After playing around and purusing the JavaDocs, my brain finally pooped out a working solution. After applying my basic and probably flawed understanding, I created a demo application with the hope of saving someone else some effort, along with consolidating my understanding.
This will not seem trivial at first, as you will have to gain an understanding. And in fact, it will never be trivial, but after gaining an incipient understanding, it will not seem difficult neither.
The demo only shows how to code drag and drop for JPanels, with the end result of a reording of the panels. The code will compile and run in Java 5.
You will want to first grab the source code, compile and run it. If you are unable to do this for some insane reason, you can view the screenshots below.
You should also look at the tracers I supplied below, then find them in the source code. This way, you will see how my inane but well documented and hopefully clean code works at runtime. I propose that this is a swell way to learn.
DragAndDropPanelsDemo.java: will compile for Java 5 (and later). Directions:
- Download the source code for DragAndDropPanelsDemo.java.
- Compile it using
javac DragAndDropPanelsDemo.java
- Execute the demo using
java DragAndDropPanelsDemo
Screenshot 1: The frame
When the frame appears, there is only a button. Click on that to add a panel.
Screenshot 2: Button clicked, panel appears
After clicking the button, the first panel appears.
Screenshot 3: Button clicked, second panel
After clicking button second time, a second panel appears.
Screenshot 4: Button clicked again, third panel
... and a third...
Screenshot 5: Button clicked again, fourth panel
... and a fourth!
Screenshot 6: Dragging panel, non-drop region
User dragging panel. When hovering over another draggable panel, cannot drop.
Screenshot 7: Dragging panel, drop region
The droppable object, a JPanel, is now over a valid drop region.
Screenshot 8: Panel dropped, all panels reorder
After dropping panel in valid region from screenshot 7, the panels reorder appropriately.
The source code contains tracers, which are simple System.out.println("Step _ of _: ...")
statements that show the path of execution at runtime.
The following will show you the order in which they will appear to standard out (e.g., your IDE's console or a command line console, etc.) during a drag and drop operation:
Step 1 of 7: Mouse pressed. Going to export our RandomDragAndDropPanel so that it is draggable.
Step 2 of 7: Returning the acceptable TransferHandler action. Our RandomDragAndDropPanel accepts Copy only.
Step 3 of 7: Casting the RandomDragAndDropPanel as Transferable. The Transferable RandomDragAndDropPanel will be queried for acceptable DataFlavors as it enters drop targets, as well as eventually present the target with the Object it transfers.
Step 4 of 7: Querying for acceptable DataFlavors to determine what is available. Our example only supports our custom RandomDragAndDropPanel DataFlavor.
// ... Step 4 repeats every time mouse drag enters a drop target's region
Step 5 of 7: The user dropped the panel. The drop(...) method will compare the drops location with other panels and reorder the panels accordingly.
Step 6 of 7: Verifying that DataFlavor is supported. Our example only supports our custom RandomDragAndDropPanel DataFlavor.
Step 7 of 7: Returning the data from the Transferable object. In this case, the actual panel is now transfered!
As you are learning how the code works, you will probably want to refer to these tracers to see the exact order of operations.
All explanations in this section are amazingly brief, and are only intended to help you muddle your way towards an understanding of the code I provided. The code is gratuitously commented, so consider this a simple road map.
I am going to discuss two roles, so it is important to get this straight immediately:
- Drag source: this is the GUI element that can be dragged. In our case, it is a JPanel, or more specifically, the
RandomDragAndDropPanel
. This class implements Transferable
.
- Drop target: this is the GUI element that will accept a drag source. In our case, it is a different JPanel, the
DemoRootPanel
. This class is assigned a DropTarget
.
The following standard classes and interfaces are necessary for drag and drop using the knowledge I gained by semi-stupid brute force:
- DataFlavor: Explains what data is transmitted when a drop occurs. This can be text, image, etc. In this case, it is an object within the JVM. A drag source can support more than one DataFlavor, and return different data for different drop targets, but ours doesn't. For more information, visit the DataFlavor JavaDoc.
- Transferable (interface): This is implemented by the drag source. This is your place to explain what DataFlavors are available, and to finally return the object associated with the drop (step 7 in the tracers).
- MouseAdapter (class): This is added as a mouse listener to the drag source. When the drag source is clicked, we'll simply export the class as a Transferable. Very simple, with most of the work done by the Java language implementation.
- TransferHandler (class): This is set to both the drag source and the drop target. Returns appropriate Transferable and negotiates the mode (in our case, copying).
- DragSourceMotionListener (interface): I implemented this in the same class that extends the TransferHandler. Perchance you want a hook. I'm not using this, but I failed to remove it.
- DropTarget (class): This is set to the drop target. Simply associates a Component with a DropTargetListener. I simply instantiated an annonymous class.
- DropTargetListener (interface): This is set in the constructor of the DropTarget. Contains the logic to handle the drop.
These are my classes, along with a brief explanation. Again, the source code is heavily commented, so use this as a reference:
- DragAndDropPanelsDemo: The main class, which extends JFrame. Sets a
DemoRootPanel
as its panel. Contains a button that adds RandomDragAndDropPanel
s.
There's a method relayout
which clears out all the components (button and all RandomDragAndDropPanel objects) from the root panel and re-adds them.
In the case of the RandomDragAndDropPanel, they are re-added in the order in which they appear in their List. When a drop occurs, the List is reordered based on the RandomDragAndDropPanel's screen location.
- DemoRootPanel: The main panel and the drop target. List the drag source,
RandomDragAndDropPanel
, DemoRootPanel uses our custom TransferHandler, DragAndDropTransferHandler
, which negotiates the drag and drop.
DemoRootPanel also uses DropTarget, which is a standard class that merely associates the DemoRootPanel with the much more interesting DemoPanelDropTargetListener
, which is our custom DropTargetListener.
- RandomDragAndDropPanel: Every time the user clicks our button, one of these is instantiated and added to the GUI.
Like the drop target DemoRootPanel, this also uses our custom TransferHandler, DragAndDropTransferHandler, to negotiate the drag and drop.
This class implements Transferable, meaning that it must implement the following three methods:
- Object getTransferData(DataFlavor flavor): returns the data we wish to "transfer" following a drop. This can be text, image, File, etc., but we are returning the DragAndDropPanelsDemo object itself. This will only work with the drop target in the same JVM process.
We could return other objects, like a String, if we had other drop targets that accept other DataFlavors.
- DataFlavor[] getTransferDataFlavors(): returns the DataFlavors that the particular Transferable object supports.
We only support our custom DataFlavor, which is the static variable in DragAndDropPanelsDemo called dragAndDropPanelDataFlavor
. If you look at the variable's instantiation, you will see how simple it is to reference an object in a DataFlavor.
Referencing an image or text is even easier, as there are static variables in DataFlavor that handle this for you. However, we aren't playing those parlor games in our example.
- boolean isDataFlavorSupported(DataFlavor flavor): called following a drop to make sure that the DataFlavor determined by the drop listener is supported. If not, nothing will happen. This is step 6 in our tracers.
- DraggableMouseListener: this is added to every instance of RandomDragAndDropPanel. It merely listens for a click and exports the RandomDragAndDropPanel as a Transferable object.
By itself, this does remarkably little. But since RandomDragAndDropPanel implements Transferable (correctly), then the RandomDragAndDropPanel instance is draggable. However, dragging does nothing until we set up proper conditions for the drop target.
This is step 1 in the tracers.
- DragAndDropTransferHandler: both the drag source, RandomDragAndDropPanel, and the drop target, DemoRootPanel, use this class, which is very simple. After DraggableMouseListener detects a click and exports the drag source, this will simple return the RandomDragAndDropPanel itself as the Transferable.
Of course, the Transferable does not have to be the drag source. We can create a separate class, which is good MVC design. But church is on Sunday, and this is a Friday.
- DemoPanelDropTargetListener: our implementation of DropTargetListener.
The simpler task this performs is to change the cursor. If you look at the code, this is stupid simple.
The more difficult and interesting task is performed in drop
. You will see tracer #5 here. Take a look.
First, we verify that the DataFlavor is the correct one — represented by the static variable DragAndDropPanelsDemo.dragAndDropPanelDataFlavor
. We the use this to retrieve the RandomDragAndDropPanel object.
Here's the crux of our logic: we grab the y location (how far up or down) of the drop and the other DragAndDropPanelsDemo's (skipping the DragAndDropPanelsDemo that was just dropped), and then sort those locations so that we have the order of the DragAndDropPanelsDemo's as they should appear in the GUI!
After they are sorted, we then politely call DragAndDropPanelsDemo's relayout
, which will clear out the panels and re-add them in the order as they appear within the List.
Of course, you could easily adapt the above to do anything besides re-order some panels. Off the top of my head, we could:
- Alert the user using JOptionPane that they are wasting their time
- Change the background of the GUI to a psudo-random color, preferably with poor contrast to the foreground color
- Create tens of thousands of tiny files to the user's desktop, possibly crashing her or his computer after it boots (you jerk)
- Send the development team an self-congradulatory email
But those are all stupid ideas. Instead, you can use this as an opportunity to store the state of the process (and later reinstate this information by dragging the file over), set text to a text field, dynamically change the GUI interface for customization, etc.