Accepting Drag-n-Drop from external sources onto an AbstractNode

When most programmers think of drag-and-drop, it is usually confined to the context of the various components within the application. For example, dragging a node from a JTree to another JTree, dragging some text to a JTextField and so forth.  I wanted my NetBeans application to have the ability to accept files, text, images, and so forth from sources outside the application.  More specifically, I wanted the user to have the ability to drag and drop onto several BeanTreeViews within the application. This goal itself did not sound particularly difficult, after all, drag-and-drop in Java is fairly easy (depending on what you are doing) and most of the plumbing is already there.  The challenge was understanding how it all worked in NetBeans using the platform objects (e.g., BeanTreeView, AbstractNode, etc).

Being new to NetBeans, the first step was for me to understand how a NetBeans application facilitates drag and drop.  As any Neophyte would do, I diligently searched the book titled, The Definitive Guide to NetBean Platform by Heiko Böck. This is a wonderful book, however the few pages in which drag-n-drop are covered are geared more towards dragging Nodes to a TreeTableView.  Good stuff, but not what I was looking for (I highly recommend this book to all those who seek to walk the path of NetBeans).

Next, I took to the internet machine and headed on over to the blog of the prolific Geertjan Wielenga.  As I had hoped, Geertjan had created an example of dragging and dropping nodes between BeanTreeViews.  This really helped break my thinking from the old straight Swing mindset of the tree container (e.g., JTree) handling the drag-n-drop, determining which node was the target, and going on from there.  Geertjan’s article may be found at http://blogs.sun.com/geertjan/entry/drag_a_node_from_a1.

My darkened mind was soon elightened as I slowly came to the realization that the BeanTreeView does most of the work for you and delegates the drag-n-drop to the nodes themselves.  Therefore, the actual code for handling the drag-n-drop belongs in the node itself as opposed to higher-level containers.  This is a really fantastic notion because this puts the desired behavior at the proper levels as well as facilitating “behavioral reuse” as the node objects are reused in other tree views and containers.

Armed with this new knowledge and filled with a sense of satisfaction after having sucessfully duplicated and executed Geertjan’s sample code, I set out to accomplish my goal of accepting drops for files, text, and virtually anything else into my platform application. This was fairly easy as the code was more or less “regular” Java and required little to no additional NetBeans expertise.

In this example, I have a node (NeophyteFolderNode) which is interested in accepting drops from outside the application,more specifically, a list of one or more files.  To accomplish the goal of interrogating and accepting we will override the getDropType method.

@Override
public PasteType getDropType(final Transferable transferable, int action, int index) {
   return getPasteType(transferable, (NeophyteFolderChildren) this.getChildren());
}

Okay, nothing too earth shattering so far. New yes, earth shattering no. The getDropType method accepts a paremeter of type Transferable. From the object that implements this interface, we can obtain the information we need to determine the type of object (supported DataFlavor) being considered for the drop operation. An object which implements Transferable may be comprised of one or more DataFlavors. In order to determine whether or not the Transferable supports a flavor in which the node is interested, we need to perform the following operation within the getDropType method:

if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
...
}

According to the snippet above, if the Transferable supports the desired DataFlavor (in this case, the javaFileListFlavor) then take some action.  In our case, we want to obtain a list of the files being considered for the drop and add them as children to the target node.  Remember, we do not need to do any special calculations to determine the target node.  The logic is being invoked within the context of the node which is the target for the drag/drop action.

 List<File> list = (List<File>) transferable.getTransferData(DataFlavor.javaFileListFlavor);
 for (File file : list) {
     /**
      * Do something here if you like. For example, copy the file
      * to some location, send it to a server, save it to
      * a database, whatever you like.
      *
      * In this example, since we are not doing anything
      * with the file, we will simply add the name to
      * the list of chilren for this node. If we were
      * doing something fancy (sending to a database and
      * such), then we would not necessarily need to
      * explicitly add the child. Rather, we could simply
      * tell the Children provider (factory, what have you)
      * to go out and refresh the list.
      */
      children.addFile(file.getName());
   }

Putting it all together, the complete code to accomplish our task of interrogating and receiving drag-and-drop messages for the node is as follows:

/**
* Determine what is being droped and wheter or not we want to accept it.
* If the item being transferred is something we want, handle it, otherwise
* just let it go.
* @param transferable The item being transferred.
* @param children The node's Children provider (for this example)
* @return PastType Something to put onto the clipboard (or null if nothing).
*/
    private PasteType getPasteType(final Transferable transferable, final NeophyteFolderChildren children) {
        return new PasteType() {

            @Override
            public Transferable paste() throws IOException {
                /**
                 * Simple example of accepting files into the node.
                 */
                if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
                    List list;
                    try {
                        list = (List) transferable.getTransferData(DataFlavor.javaFileListFlavor);
                        for (File file : list) {
                            /**
                             * Do something here if you like. For example, copy the file
                             * to some location, send it to a server, save it to
                             * a database, whatever you like.
                             */
                            /**
                             * In this example, since we are not doing anything
                             * with the file, we will simply add the name to
                             * the list of chilren for this node. If we were
                             * doing something fancy (sending to a database and
                             * such), then we would not necessarily need to
                             * explicitly add the child. Rather, we could simply
                             * tell the Children provider (factory, what have you)
                             * to go out and refresh the list.
                             */
                            children.addFile(file.getName());
                        }
                    } catch (UnsupportedFlavorException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }
                // In this example, we will not put anything onto the Clipboard
                return null;
            }
        };
    }

The same approach can be taken to accept many types of DataFlavors from your desktop, a web browser, and so on.   For example, the following snippet allows the node to accept Transferables that support the textPlainUnicodeFlavor.

if (transferable.isDataFlavorSupported(DataFlavor.getTextPlainUnicodeFlavor())) {
  BufferedReader reader= null;
   try {
     reader = new        BufferedReader(DataFlavor.getTextPlainUnicodeFlavor().getReaderForText(transferable));
     StringBuffer buffer = new StringBuffer();
     String data = null;
     String newLine = System.getProperty("line.separator");
     while ((data = reader.readLine()) != null) {
       buffer.append(data);
       buffer.append(newLine);
     }

     /**
     * Do something with this buffer. For this example
     * we will simply print it to the console.
     */
      System.out.println(buffer.toString());
   } catch (UnsupportedFlavorException ex) {
  Exceptions.printStackTrace(ex);
  } finally {
     if (null!=reader) {
     reader.close();
    }
  }
}

There are most likely a wide variety of ways to accomplish what I have demonstrated in this article.  I welcome any constructive feedback or improvements on this method.

Download the source code for this article: Dave Rigsby: NetBeans Drag and Drop Article 1

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>