Swing Drag and Drop on non supported components
The 1.4 release of Java SE introduced a much needed overhaul for the Drag and Drop subsystem. I sweated blood back in 1999 to make it reasonably work for a client’s requirements. That led to an hour long 1999 JavaOne talk (that was before the marketing crap seeped into JavaOne) and the now out of date DnD FAQ.
Many components such as JList, JTextField and others now have DnD baked into them. You call setDragEnabled(true) and provide them with a TransferHandler. The more complicated components like JTable have lots of customization possible through new support classes.
But what about the non-supported components like a JLabel? Or even your own components?
There are naiive approaches to adding drag capabilities to a non-supported component. This article actually rips off a Sun DnD document without attribution (like the rest of the content on that site). Here the drag is initiated on a mouse pressed event. Every other drag in the known universe is commenced after the mouse moves a bit. Additionally if you start the drag on a mouse press then you cannot notify listeners for any other mouse event since DnD takes over.
The real way it’s done is in the plaf layer. But maybe you just want a simple subclass of JComponent and don’t want to eat the whole MVC Swing elephant.
Take a look at Shannon Hickey’s excellent DnD contributions to the basic plaf. For recognizing the drag gesture take a look at javax.swing.plaf.basic.DragRecognitionSupport.
I borrowed some of his code for a simple JComponent subclass. First I enabled the events in the constructor like this.
1 |
this.enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); |
Then I added two instance variables and overrode the event processing methods. On mouse press I simply saved (into those two instance variables) the mouse event and retrieved the number of pixels that the user needs to move the mouse in order to initiate the drag. On mouse release I null out the event (and in the example do other non DnD related stuff).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
private int motionThreshold = 5; private MouseEvent dndArmedEvent; @Override protected void processMouseEvent(MouseEvent e) { switch (e.getID()) { case MouseEvent.MOUSE_PRESSED: this.dndArmedEvent = e; this.motionThreshold = DragSource.getDragThreshold(); break; case MouseEvent.MOUSE_RELEASED: if (this.isInside) // non DnD stuff this.notifyListeners(); this.dndArmedEvent = null; break; etc. |
Then to actually initiate the drag the mouse dragged event checks the distance the mouse has been dragged and then begins if the distance exceeds the threshold. This is pretty much what Shannon does in the plaf – but he does a lot more checking. Take a look at his code for those checks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Override protected void processMouseMotionEvent(MouseEvent me) { switch (me.getID()) { case MouseEvent.MOUSE_DRAGGED: int dx = Math.abs(me.getX() - dndArmedEvent.getX()); int dy = Math.abs(me.getY() - dndArmedEvent.getY()); if ((dx > motionThreshold) || (dy > motionThreshold)) { TransferHandler th = this.getTransferHandler(); th.exportAsDrag(this, me, TransferHandler.COPY); } break; } } |