As a refresher, certain JComponents already support dropping. They include:
- JEditorPane
- JFormattedTextField
- JPasswordField
- JTextArea
- JTextField
- JTextPane
- JColorChooser
For those JComponents that don't support dropping inherintly, we can add drop functionality to them. As with dragging, we will need the TransferHandler class. In addition, we will be using the DropTargetListener interface and DataFlavor class. Even more so with dragging, it is very important to understand how the JComponent you are adding dropping functionality to operates. Otherwise, it can be quite confusing. So let's get started with making a JTable that accepts our DnDButtons. Basically, the process for dropping starts when the DropTargetDropEvent is fired. We then check to make sure the DataFlavors from the Transferable are compatible with the JComponent we want to drop on. Essentially, the DataFlavor objects are just markers. Finally, we insert the Transferable's transferData into the JComponent.
public class DnDTable extends JTable implements DropTargetListener{ //the JTable data private Object[][] rows; private Object[] columns; private DropTarget target; //initialize the JTable with the data public DnDTable(int r, int c, Object[][] data){ super(data, data[0]); rows = data; columns = data[0]; this.setRowHeight(25); TableCellRenderer render = getDefaultRenderer(JButton.class); setDefaultRenderer(JButton.class, new ButtonRenderer(render)); rows = data; //and set it to render JComponents setModel(new ButtonModel()); //mark this a DropTarget target = new DropTarget(this,this); //have it utilize a custom transfer handler setTransferHandler(new MyTransferHandler()); } public void dragEnter(DropTargetDragEvent dtde) {} public void dragOver(DropTargetDragEvent dtde) {} public void dropActionchanged(DropTargetDragEvent dtde) {} public void dragExit(DropTargetEvent dte) {} //This is what happens when a Drop occurs public void drop(DropTargetDropEvent dtde) { try { //get the Point where the drop occurred Point loc = dtde.getLocation(); //get Transfer data Transferable t = dtde.getTransferable(); //get the Data flavors transferred with the Transferable DataFlavor[] d = t.getTransferDataFlavors(); DnDButton tempButton = (DnDButton)t.getTransferData(d[0]); //and if the DataFlavors match for the DnDTable //(ie., we don't want an ImageFlavor marking an image transfer) if(getTransferHandler().canImport(this, d)){ //then import the Draggable JComponent and repaint() the JTable ((MyTransferHandler)getTransferHandler()).importData(this, (DnDButton)t.getTransferData(d[0]), loc); repaint(); } else return; } catch (UnsupportedFlavorException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } finally{ dtde.dropComplete(true); } } //Class to enable JTable to display JButtons class ButtonRenderer implements TableCellRenderer{ private TableCellRenderer defaultRenderer; public ButtonRenderer(TableCellRenderer defaultRenderer){ this.defaultRenderer = defaultRenderer; } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if(value instanceof Component) return (Component)value; return defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); } }//end ButtonRenderer //enables JTable to display JButtons class ButtonModel extends AbstractTableModel{ public int getRowCount() { return rows.length; } public int getColumnCount() { return columns.length; } public Object getValueAt(int rowIndex, int columnIndex) { return rows[rowIndex][columnIndex]; } public boolean isCellEditable(int row, int column) { return false; } public Class getColumnClass(int column) { return DnDButton.class; } }//end inner class class MyTransferHandler extends TransferHandler{ //tests for a valid JButton DataFlavor public boolean canImport(JComponent c, DataFlavor[] f){ DataFlavor temp = new DataFlavor(DnDButton.class, "JButton"); for(DataFlavor d:f){ if(d.equals(temp)) return true; } return false; } //add the data into the JTable public boolean importData(JComponent comp, Transferable t, Point p){ int row = rowAtPoint(p); int col = columnAtPoint(p); //if a value is in the JTable cell, do nothing if(model.getValueAt(row, col) != null) return false; try { DnDButton tempButton = (DnDButton)t.getTransferData(new DataFlavor(DnDButton.class, "JButton")); rows[row][col] = tempButton; } catch (UnsupportedFlavorException ex) { Logger.getLogger(DnDTable.class.getName()).log(Level.SEVERE, null, ex); } catch (IOException ex) { Logger.getLogger(DnDTable.class.getName()).log(Level.SEVERE, null, ex); } return true; } } }
Playing Both Sides of the Drag and Drop
Certain JComponents like JTable, JList and JTree (and others) can be used to play both sides of the Drag and Drop operation. So for example, we can drag our DnDButtons between cells in a JTable. With JTrees or JLists, this applies to dragging and dropping nodes or elements being displayed as well. While this may sound, complex, this is actually very simple and requires very few extras beyond what was covered in my tutorial on Dragging. We still implement DragSourceListener and DragGestureListener, and we still use the DragSource class and TransferHandler to handle exporting the Transferable. With JTable, there is the minor cleanup operation of removing the element from the originating cell after we move it. Let's examine the final DnDTable to see these concepts in action.
public class DnDTable extends JTable implements DropTargetListener, DragSourceListener, DragGestureListener{ //the JTable data private Object[][] rows; private Object[] columns; private DropTarget target; private DragSource source; private MyTransferHandler handler; //this point tells us where the Drag initiated //to allow us to get the appropriate DnDButton private Point dragOrigin; public DnDTable(int r, int c, Object[][] data){ super(data, data[0]); rows = data; columns = data[0]; dragOrigin = new Point(); this.setRowHeight(25); TableCellRenderer render = getDefaultRenderer(JButton.class); setDefaultRenderer(JButton.class, new ButtonRenderer(render)); rows = data; setModel(new ButtonModel()); target = new DropTarget(this,this); source = new DragSource(); source.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_MOVE, this); setTransferHandler((handler = new MyTransferHandler())); this.setDragEnabled(true); this.setDropMode(DropMode.INSERT); } public void dragEnter(DropTargetDragEvent dtde) {} public void dragOver(DropTargetDragEvent dtde) {} public void dropActionchanged(DropTargetDragEvent dtde) {} public void dragExit(DropTargetEvent dte) {} public void drop(DropTargetDropEvent dtde) { try { Point loc = dtde.getLocation(); Transferable t = dtde.getTransferable(); DataFlavor[] d = t.getTransferDataFlavors(); DnDButton tempButton = (DnDButton)t.getTransferData(d[0]); if(handler.canImport(this, d)){ System.out.println("Import"); handler.importData(this, (DnDButton)t.getTransferData(d[0]), loc); dtde.acceptDrop(DnDConstants.ACTION_MOVE); //we have added that once the drag is complete, //we remove the original DnDButton from the JTable //to simulate a move, not a copy rows[dragOrigin.y][dragOrigin.x] = null; repaint(); //reset the dragOrigin to prevent an accidental modification of another cell dragOrigin = new Point(); } else return; } catch (UnsupportedFlavorException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } finally{ dtde.dropComplete(true); } } public void dragEnter(DragSourceDragEvent dsde) {} public void dragOver(DragSourceDragEvent dsde) {} public void dropActionchanged(DragSourceDragEvent dsde) {} public void dragExit(DragSourceEvent dse) {} public void dragDropEnd(DragSourceDropEvent dsde) {} //notice we still have our dragGestureRecognized() method to initiate the drag public void dragGestureRecognized(DragGestureEvent dge) { if(getSelectedRow() == 0 || getSelectedColumn() == 0) return; dragOrigin.x = getSelectedColumn(); dragOrigin.y = getSelectedRow(); if(this.getValueAt(getSelectedRow(), getSelectedColumn()) == null) return; else if(!(getValueAt(getSelectedRow(), getSelectedColumn()) instanceof DnDButton)) return; Transferable temp = handler.createTransferable(c); source.startDrag(dge, DragSource.DefaultMoveDrop, temp, this); } //Class to enable JTable to display JButtons class ButtonRenderer implements TableCellRenderer{ private TableCellRenderer defaultRenderer; public ButtonRenderer(TableCellRenderer defaultRenderer){ this.defaultRenderer = defaultRenderer; } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if(value instanceof Component) return (Component)value; return defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); } }//end ButtonRenderer //enables JTable to display JButtons class ButtonModel extends AbstractTableModel{ public int getRowCount() { return rows.length; } public int getColumnCount() { return columns.length; } public Object getValueAt(int rowIndex, int columnIndex) { return rows[rowIndex][columnIndex]; } public boolean isCellEditable(int row, int column) { return false; } public Class getColumnClass(int column) { return DnDButton.class; } }//end inner class //notice how the TransferHandler handles exporting as well as importing class MyTransferHandler extends TransferHandler{ public boolean canImport(JComponent c, DataFlavor[] f){ DataFlavor temp = new DataFlavor(DnDButton.class, "JButton"); for(DataFlavor d:f){ if(d.equals(temp)) return true; } return false; } public boolean importData(JComponent comp, Transferable t, Point p){ DnDTable table = (DnDTable)comp; ButtonModel model = (ButtonModel)table.getModel(); int row = rowAtPoint(p); int col = columnAtPoint(p); if(model.getValueAt(row, col) != null) return false; else if(col == dragOrigin.x && row == dragOrigin.y) return false; try { DnDButton tempButton = (DnDButton)t.getTransferData(DataFlavor.imageFlavor); rows[row][col] = tempButton; } catch (UnsupportedFlavorException ex) { Logger.getLogger(DnDTable.class.getName()).log(Level.SEVERE, null, ex); } catch (IOException ex) { Logger.getLogger(DnDTable.class.getName()).log(Level.SEVERE, null, ex); } return true; } public Transferable createTransferable(JComponent c){ DnDTable table = (DnDTable)c; int row = table.getSelectedRow(); int col = table.getSelectedColumn(); if(table.getValueAt(row, col) instanceof DnDButton) return (DnDButton)table.getValueAt(row, col); return null; } } }