How do I drag and drop a row in a JTable?

1,332

Solution 1

Check out the drag and drop section of the Java Tutorial. There are some examples on how to implement this for JTable.

Solution 2

The following allows JTable re-ordering of a single dragged row:

  table.setDragEnabled(true);
  table.setDropMode(DropMode.INSERT_ROWS);
  table.setTransferHandler(new TableRowTransferHandler(table)); 

Your TableModel should implement the following to allow for re-ordering:

public interface Reorderable {
   public void reorder(int fromIndex, int toIndex);
}

This TransferHandler class handles the drag & drop, and calls reorder() on your TableModel when the gesture is completed.

/**
 * Handles drag & drop row reordering
 */
public class TableRowTransferHandler extends TransferHandler {
   private final DataFlavor localObjectFlavor = new ActivationDataFlavor(Integer.class, "application/x-java-Integer;class=java.lang.Integer", "Integer Row Index");
   private JTable           table             = null;

   public TableRowTransferHandler(JTable table) {
      this.table = table;
   }

   @Override
   protected Transferable createTransferable(JComponent c) {
      assert (c == table);
      return new DataHandler(new Integer(table.getSelectedRow()), localObjectFlavor.getMimeType());
   }

   @Override
   public boolean canImport(TransferHandler.TransferSupport info) {
      boolean b = info.getComponent() == table && info.isDrop() && info.isDataFlavorSupported(localObjectFlavor);
      table.setCursor(b ? DragSource.DefaultMoveDrop : DragSource.DefaultMoveNoDrop);
      return b;
   }

   @Override
   public int getSourceActions(JComponent c) {
      return TransferHandler.COPY_OR_MOVE;
   }

   @Override
   public boolean importData(TransferHandler.TransferSupport info) {
      JTable target = (JTable) info.getComponent();
      JTable.DropLocation dl = (JTable.DropLocation) info.getDropLocation();
      int index = dl.getRow();
      int max = table.getModel().getRowCount();
      if (index < 0 || index > max)
         index = max;
      target.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      try {
         Integer rowFrom = (Integer) info.getTransferable().getTransferData(localObjectFlavor);
         if (rowFrom != -1 && rowFrom != index) {
            ((Reorderable)table.getModel()).reorder(rowFrom, index);
            if (index > rowFrom)
               index--;
            target.getSelectionModel().addSelectionInterval(index, index);
            return true;
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
      return false;
   }

   @Override
   protected void exportDone(JComponent c, Transferable t, int act) {
      if ((act == TransferHandler.MOVE) || (act == TransferHandler.NONE)) {
         table.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      }
   }

}

Solution 3

I like Soley's modifications, but his code relies on an external library, and I'm not sure where he got it from, so I re-wrote it so that you don't need the TableUtil class...

 @Override
   public boolean importData(TransferHandler.TransferSupport info) {
     JTable target = (JTable) info.getComponent();
        JTable.DropLocation dl = (JTable.DropLocation) info.getDropLocation();
        int index = dl.getRow();
        int max = table.getModel().getRowCount();
        if (index < 0 || index > max) {
            index = max;
        }
        target.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

        try {
            Integer rowFrom = (Integer) info.getTransferable().getTransferData(localObjectFlavor);
            if (rowFrom != -1 && rowFrom != index) {

                int[] rows = table.getSelectedRows();
                int iter = 0;
                for (int row : rows) {
                    if (index > row) {
                        index--;
                        ((Reorderable) table.getModel()).reorder(row - iter, index);
                    }

                    else {
                        ((Reorderable) table.getModel()).reorder(row, index);
                    }
                    index++;
                    iter++;
                }

                target.getSelectionModel().addSelectionInterval(index, index);

                return true;
            }

      } catch (Exception e) {
            String error = e.getMessage();
            JOptionPane.showMessageDialog(null, error, "Error", JOptionPane.ERROR_MESSAGE);
      }
      return false;
   }
Share:
1,332
MARCOS GARCES
Author by

MARCOS GARCES

Updated on June 23, 2020

Comments

  • MARCOS GARCES
    MARCOS GARCES almost 4 years

    I'm trying to install microk8s, using Ansible.

    I get the error : "No snap matching 'microk8s' available"

    I'm using WSL 2 (Ubuntu 20.04), and snap version 2.44.3+20.04.

    My configuration:

    - name: Install microk8s
      snap:
        name:
          - microk8s
        classic: yes
      become: true
    

    Does anyone know how to fix this?

    • a2f0
      a2f0 over 2 years
      For anybody having trouble with this on Ubuntu 20 (without WSL), make sure to run the command to install the collection ansible-galaxy collection install community.general
  • Koobz
    Koobz over 12 years
    I was getting exceptions upon transfer that I fixed by changing localObjectFlavor to: private final DataFlavor localObjectFlavor = new DataFlavor(Integer.class, "Integer Row Index");
  • Stephan
    Stephan over 11 years
    +1 But unfortunately this will not work for multiple row selection.
  • pstanton
    pstanton about 11 years
    +1 - a couple of simple modifications and this works perfectly !
  • David
    David over 10 years
    Nice example! Although I noticed a bug in exportDone. If you drag the selected value outside the component and release the curser will keep the "copy icon". I corrected this by modifying the if statement in exportDone to "act == TransferHandler.MOVE || act == TransferHandler.NONE".
  • Ignas2526
    Ignas2526 about 8 years
    Great code. One problem I had with it, is that I kept getting java.lang.ClassCastException: java.lang.Integer cannot be cast to java.io.InputStream error. Using instead localObjectFlavor = new ActivationDataFlavor(Integer.class, "application/x-java-Integer;class=java.lang.Integer", "Integer Row Index") solved the error.
  • ImJustACowLol
    ImJustACowLol over 4 years
    I find the usage of TransferHandler highly complicated and unnecessary... I would propose the solution that makes use of Mouse(Motion)Listeners: stackoverflow.com/questions/58043707/…
  • MARCOS GARCES
    MARCOS GARCES over 3 years
    I get: nsenter: failed to parse pid: '-a'
  • mvmn
    mvmn over 2 years
    What is TableUtil?
  • Soley
    Soley over 2 years
    It is a class that make working with rows simple. The method name is describe what we will receive from them. You can replace them by your own methods, or use Table's own methods.