Reordering JList with Drag and Drop

UserOrNotAnUser picture UserOrNotAnUser · May 16, 2013 · Viewed 14.1k times · Source

I encountered a problem regarding reordering elements in a JList using Drag and Drop. This following code is a modification of a code where you could drag elements from one JList to another (worked only one way). I tried to make it usable for only one JList, but the elements can't even be dragged out of the list. So I guess it can't be done this way. Any ideas what I'm doing wrong or not taking into consideration?

The idea is to get it to work for a Jlist with thumbnails, but since I can't even get it to work with just strings... I have been looking into several D'n'D tutorials, but still I can't get it to work. Any help is appreciated.

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.datatransfer.*;
import java.io.IOException;

public class DragAndDrop extends JFrame {


DefaultListModel<String> transport = new DefaultListModel<String>();
JList<String> transportList = new JList<String>(transport);

public DragAndDrop() {
    setLayout(new FlowLayout());

    transport.addElement("Bike");
    transport.addElement("Car");
    transport.addElement("Truck");
    transport.addElement("Boat");

    JScrollPane transportScroll = new JScrollPane(transportList);
    transportScroll.setBorder(new TitledBorder("Transportation"));

    add(transportScroll);

    transportList.setDragEnabled(true);

    transportList.setTransferHandler(new TransferHandler() {
        int index;  

        @Override
        public int getSourceActions(JComponent comp) {
            return COPY_OR_MOVE;
        }

        @Override
        public Transferable createTransferable(JComponent comp) {
            index = transportList.getSelectedIndex(); 
            return new StringSelection(transportList.getSelectedValue());
        }


        @Override
        public void exportDone( JComponent comp, Transferable trans, int action ) {
            if (action==MOVE) {
                transport.remove(index);
            }
        }
    });

    transportList.setDropMode(DropMode.ON);

    transportList.setTransferHandler(new TransferHandler() {
        @Override
        public boolean canImport(TransferHandler.TransferSupport support) {
            // data of type string?
            return support.isDataFlavorSupported(DataFlavor.stringFlavor);
        }

        @Override
        public boolean importData(TransferHandler.TransferSupport support) {
            try {
                // convert data to string
                String s = (String)support.getTransferable().getTransferData(DataFlavor.stringFlavor);
                JList.DropLocation dl = (JList.DropLocation)support.getDropLocation();
                transport.add(dl.getIndex(),s);
                return true;
            } 
            catch (UnsupportedFlavorException e) {} 
            catch (IOException e) {}

            return false;
        }
    });

    pack();

    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setVisible(true);
}

public static void main(String[] args) {
    new DragAndDrop();
}

}

PS. Sorry if this turns out to be a re-post.

EDIT I think I got it fixed: had to different transferHandlers - should only have one with all the methods from the second one as well.

Answer

aterai picture aterai · May 16, 2013

enter image description here

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DragSource;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Objects;
// import javax.activation.ActivationDataFlavor;
// import javax.activation.DataHandler;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.DropMode;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.TransferHandler;
import javax.swing.UIManager;
import javax.swing.WindowConstants;

public class DragAndDropTest {
  public JComponent makeUI() {
    DefaultListModel<Thumbnail> m = new DefaultListModel<>();
    for (String s : Arrays.asList("error", "information", "question", "warning")) {
      m.addElement(new Thumbnail(s));
    }

    JList<Thumbnail> list = new JList<>(m);
    list.getSelectionModel().setSelectionMode(
      ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    list.setTransferHandler(new ListItemTransferHandler());
    list.setDropMode(DropMode.INSERT);
    list.setDragEnabled(true);
    // https://java-swing-tips.blogspot.com/2008/10/rubber-band-selection-drag-and-drop.html
    list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
    list.setVisibleRowCount(0);
    list.setFixedCellWidth(80);
    list.setFixedCellHeight(80);
    list.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

    list.setCellRenderer(new ListCellRenderer<Thumbnail>() {
      private final JPanel p = new JPanel(new BorderLayout());
      private final JLabel icon = new JLabel((Icon)null, JLabel.CENTER);
      private final JLabel label = new JLabel("", JLabel.CENTER);

      @Override
      public Component getListCellRendererComponent(
        JList<? extends Thumbnail> list, Thumbnail value, int index,
        boolean isSelected, boolean cellHasFocus) {
        icon.setIcon(value.icon);
        label.setText(value.name);
        label.setForeground(isSelected ? list.getSelectionForeground()
                            : list.getForeground());
        p.add(icon);
        p.add(label, BorderLayout.SOUTH);
        p.setBackground(isSelected ? list.getSelectionBackground()
                        : list.getBackground());
        return p;
      }
    });
    return new JScrollPane(list);
  }

  public static void main(String[] args) {
    EventQueue.invokeLater(() -> createAndShowGUI());
  }

  public static void createAndShowGUI() {
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.getContentPane().add(new DragAndDropTest().makeUI());
    f.setSize(320, 240);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}

class Thumbnail implements Serializable {
  public final String name;
  public final Icon icon;
  public Thumbnail(String name) {
    this.name = name;
    this.icon = UIManager.getIcon("OptionPane." + name + "Icon");
  }
}

// @camickr already suggested above.
// https://docs.oracle.com/javase/tutorial/uiswing/dnd/dropmodedemo.html
@SuppressWarnings("serial")
class ListItemTransferHandler extends TransferHandler {
  protected final DataFlavor localObjectFlavor;
  protected int[] indices;
  protected int addIndex = -1; // Location where items were added
  protected int addCount; // Number of items added.

  public ListItemTransferHandler() {
    super();
    // localObjectFlavor = new ActivationDataFlavor(
    //   Object[].class, DataFlavor.javaJVMLocalObjectMimeType, "Array of items");
    localObjectFlavor = new DataFlavor(Object[].class, "Array of items");
  }

  @Override
  protected Transferable createTransferable(JComponent c) {
    JList<?> source = (JList<?>) c;
    c.getRootPane().getGlassPane().setVisible(true);

    indices = source.getSelectedIndices();
    Object[] transferedObjects = source.getSelectedValuesList().toArray(new Object[0]);
    // return new DataHandler(transferedObjects, localObjectFlavor.getMimeType());
    return new Transferable() {
      @Override public DataFlavor[] getTransferDataFlavors() {
        return new DataFlavor[] {localObjectFlavor};
      }
      @Override public boolean isDataFlavorSupported(DataFlavor flavor) {
        return Objects.equals(localObjectFlavor, flavor);
      }
      @Override public Object getTransferData(DataFlavor flavor)
            throws UnsupportedFlavorException, IOException {
        if (isDataFlavorSupported(flavor)) {
          return transferedObjects;
        } else {
          throw new UnsupportedFlavorException(flavor);
        }
      }
    };
  }

  @Override
  public boolean canImport(TransferSupport info) {
    return info.isDrop() && info.isDataFlavorSupported(localObjectFlavor);
  }

  @Override
  public int getSourceActions(JComponent c) {
    Component glassPane = c.getRootPane().getGlassPane();
    glassPane.setCursor(DragSource.DefaultMoveDrop);
    return MOVE; // COPY_OR_MOVE;
  }

  @SuppressWarnings("unchecked")
  @Override
  public boolean importData(TransferSupport info) {
    TransferHandler.DropLocation tdl = info.getDropLocation();
    if (!canImport(info) || !(tdl instanceof JList.DropLocation)) {
      return false;
    }

    JList.DropLocation dl = (JList.DropLocation) tdl;
    JList target = (JList) info.getComponent();
    DefaultListModel listModel = (DefaultListModel) target.getModel();
    int max = listModel.getSize();
    int index = dl.getIndex();
    index = index < 0 ? max : index; // If it is out of range, it is appended to the end
    index = Math.min(index, max);

    addIndex = index;

    try {
      Object[] values = (Object[]) info.getTransferable().getTransferData(localObjectFlavor);
      for (int i = 0; i < values.length; i++) {
        int idx = index++;
        listModel.add(idx, values[i]);
        target.addSelectionInterval(idx, idx);
      }
      addCount = values.length;
      return true;
    } catch (UnsupportedFlavorException | IOException ex) {
      ex.printStackTrace();
    }

    return false;
  }

  @Override
  protected void exportDone(JComponent c, Transferable data, int action) {
    c.getRootPane().getGlassPane().setVisible(false);
    cleanup(c, action == MOVE);
  }

  private void cleanup(JComponent c, boolean remove) {
    if (remove && Objects.nonNull(indices)) {
      if (addCount > 0) {
        // https://github.com/aterai/java-swing-tips/blob/master/DragSelectDropReordering/src/java/example/MainPanel.java
        for (int i = 0; i < indices.length; i++) {
          if (indices[i] >= addIndex) {
            indices[i] += addCount;
          }
        }
      }
      JList source = (JList) c;
      DefaultListModel model = (DefaultListModel) source.getModel();
      for (int i = indices.length - 1; i >= 0; i--) {
        model.remove(indices[i]);
      }
    }

    indices = null;
    addCount = 0;
    addIndex = -1;
  }
}