I've blogged quite a lot (especially, in the context below, here, back in 2009) about dragging and dropping Nodes into various places. One place I hadn't looked at yet is inspired by the question of the day, provided by Geoffrey Waardenburg in a comment in this blog today: how to drag a Node into an empty ListView. So, rather than dragging a Node onto another Node, the scenario is that you have an empty ListView, i.e., containing no Nodes at all, like this:
Now you want to drag one or more Nodes into that ListView and then, on the drop, display related data there.
The first thing that you need is to have a reference to the JList which is contained within the ListView. Once you have that, you can set it as a drop target.
- Create a Custom ListView. Here's the code of my ListView, which exists for no other reason than to expose the underlying and protected JList:
import javax.swing.JList; import org.openide.explorer.view.ListView; public class MyListView extends ListView { public JList jList; @Override protected JList createList() { jList = super.createList(); return jList; } public JList getjList() { return jList; } }
- Listen for Drops on the JList in the ChildFactory. An unorthodox thing to do, yet I challenge anyone to solve this in a different/better way:
import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.swing.JList; import org.my.domain.Customer; import org.openide.awt.StatusDisplayer; import org.openide.nodes.AbstractNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; class NameChildFactory extends ChildFactory<Customer> { private ArrayList<Customer> names = new ArrayList<Customer>(); private final JList list; public NameChildFactory(JList list) { this.list = list; MyDropTargetListener dtl = new MyDropTargetListener(); DropTarget dt = new DropTarget(list, dtl); dt.setDefaultActions(DnDConstants.ACTION_COPY_OR_MOVE); dt.setActive(true); } @Override protected boolean createKeys(List<Customer> list) { list.addAll(names); return true; } @Override protected Node createNodeForKey(final Customer name) { Node node = new AbstractNode(Children.LEAF); node.setDisplayName(name.getName()); return node; } public class MyDropTargetListener implements DropTargetListener { @Override public void drop(DropTargetDropEvent dtde) { if (dtde.isDataFlavorSupported(Customer.DATA_FLAVOR)) { try { Object transData = dtde.getTransferable(). getTransferData(Customer.DATA_FLAVOR); if (transData instanceof Customer) { dtde.acceptDrop(DnDConstants.ACTION_COPY); Customer c = (Customer) dtde.getTransferable(). getTransferData(Customer.DATA_FLAVOR); StatusDisplayer.getDefault().setStatusText(c.getName()); names.add(c); refresh(true); } } catch (UnsupportedFlavorException ufe) { dtde.rejectDrop(); dtde.dropComplete(true); } catch (IOException ioe) { dtde.rejectDrop(); dtde.dropComplete(false); } } else { dtde.rejectDrop(); dtde.dropComplete(false); } } @Override public void dragEnter(DropTargetDragEvent dtde) {} @Override public void dragExit(DropTargetEvent dtde) {} @Override public void dragOver(DropTargetDragEvent dtde) {} @Override public void dropActionChanged(DropTargetDragEvent dtde) {} } }
And that's all you need to do. When a drop takes place on the JList, update the list held by the ChildFactory, and refresh the node hierarchy. (Go here for all the other code in this sample so that you're able to recreate it.)