Friday, March 26, 2004

Extending the DnD in Swing = reinventing the wheel


The Swing's DnD functionality looks very extendable at a first glance, but as more as I dig deeper into this part of the SDK I understand that it is not 100% true. Adding new DnD functionality to a component, forces the developer to reinvent the wheel every time. Why do the developer have to redo already implemented functionality to extend a components DnD actions?




Many of the JComponents in the swing package supports drag and drop actions. Those components already have a nice feature rich implementation that handles the dragging and dropping of objects to/from the component. The problem arises when the developer wants to add a new flavor to the components DnD functionality, ie extend so the component can import new types of objects. Each JComponent can only have one TransferHandler, and this is the one used during a DnD action.




Once again I'm in the middle of adding DnD to the email client (see my previous blog). This time I would like to add drag and drop of files into a new email, ie. add them as attachments. Other native email clients lets the user drag files onto the text area and the attachment bar. When the user drops the files in either of the target components, they are added to the emails attachment list. Now this was no problem adding the DnD of files to a attachment component but when I wanted to add this feature to the editor panes DnD support, I stumbled upon this problem. The editor pane already has a TransferHandler that supports strings/texts/Unicode data flavors. I would only like to add the fileList data flavor so it would also accept files.




The problem starts with that the TransferHandlers that are used by the JEditorPane components is package private, and therefore I cant extend them to get the functionality I want. I don't want to re-implement all those features in my own version, since that would be a waste of time and bloat the code. Then I thought of using the chain of responsibility design pattern to merge the two transfer handlers into one. Ie use one as the primary transfer handler, and then if the primary doesn't support a certain flavor it will try the other handler(s) if they can import a certain flavor. This to me looked like a fine idea, and I developed a nice class to work as one TransferHandler, but actually handling several. Everything went fine until the user tries to drag and drop from a component that has this object.




During that drag and drop action, it wants to create a transferable object through the protected method createTransferable(). BUT the method is called by at javax.swing.TransferHandler$DragHandler.dragGestureRecognized(TransferHandler.java:710) method, (outside the TransferHandler class) and it uses the fact that they are in the same package, ie they can break the protected access modifier. Now I have no clue on how to create the real transferable object, since in my implementation (see below) tries to do this on the primary transfer handler, but its createTransferable() method is protected and therefore I cant reach it, I'm not in the same package so I can "cheat" as the DragHandler does.




Once again, there is a problem in the design of the Swings DnD, but at least for this problem I have a workaround. I get around the problem by calling the needed protected methods using the reflection API, and making them public. But this to me is not the correct way of doing it, I should be able to call those methods in TransferHandler without relying on using reflection. There is already a feature request at Suns bug report system, and all I can do is hope that they change it in coming releases, but for now I at least got a solution. If you would like to use it, it is part of the Frappucino module in the Columba project. Frappucino is a collection of widgets that eases the development of Swing UIs. Check the link section for the class.