Page 1 of 1

Java Drag and Drop Tutorial: Part 2- Dropping Rate Topic: -----

#1 macosxnerd101  Icon User is online

  • Self-Trained Economist
  • member icon




Reputation: 10390
  • View blog
  • Posts: 38,451
  • Joined: 27-December 08

Posted 22 January 2011 - 07:20 PM

In my last tutorial, I introduced the basics of dragging using the Java DnD API. This tutorial focuses on the dropping aspect of the DnD process, as well as using a JComponent like JTable to play both sides of the drag and drop processes.

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;
            }

        }
}



Is This A Good Question/Topic? 4
  • +

Replies To: Java Drag and Drop Tutorial: Part 2- Dropping

#2 Nemesis89  Icon User is offline

  • D.I.C Head

Reputation: 0
  • View blog
  • Posts: 56
  • Joined: 20-January 14

Posted 20 January 2014 - 07:03 AM

Hello congratulations for the tutorial on DnD, it's great! I am facing this problem for a project, but I have to drag Imagelcon contained in JButton. In DataFlavor you entered as parameters "String", I would like to know if I can somehow enter the ImageIcon.
Thank you so much
Was This Post Helpful? 0
  • +
  • -

#3 macosxnerd101  Icon User is online

  • Self-Trained Economist
  • member icon




Reputation: 10390
  • View blog
  • Posts: 38,451
  • Joined: 27-December 08

Posted 21 January 2014 - 07:59 AM

It's been quite a while since I've worked with the DnD API. I would imagine the TransferHandler would be the tool you want to use. In my first tutorial, the DnDButton is the Transferable object. You could probably use the importData() method to import the ImageIcon correctly.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1