Reordering JList with Drag and Drop

匆匆过客 提交于 2019-11-28 13:25:26

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

See the Drop Demo from the Swing tutorial on DnD for an example that will drop on the same JList or another JList.

As the OP noted in their edit to the original question, the problem in the example given was that there were two transfer handlers and as camickr rightly pointed out in their answer, there is an example in the Java Tutorials which will work.

The problem with the example in the Java Tutorials is that, when using DropMode.INSERT and moving an item in the current JList to before the selected index, the item is duplicated. This deletes an item in the JList, puts a duplicate of the item in the place you wanted it to go, and leaves the original selected item as it is.

So, for those interested, here is an example which fixes that problem based upon the JList<String> example provided in the OP's question.

import java.awt.EventQueue;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

import javax.swing.DefaultListModel;
import javax.swing.DropMode;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.TransferHandler;

@SuppressWarnings("serial")
public class GUI extends JFrame {
    protected GUI() {
        super("Simple Rearrangeable List");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        createPanel();
        setBounds(10, 10, 350, 500);
        setVisible(true);
    }

    private void createPanel() {
        DefaultListModel<String> strings = new DefaultListModel<String>();

        for(int i = 1; i <= 100; i++) {
            strings.addElement("Item " + i);
        }

        JList<String> dndList = new JList<String>(strings);
        dndList.setDragEnabled(true);
        dndList.setDropMode(DropMode.INSERT);
        dndList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        dndList.setTransferHandler(new TransferHandler() {
            private int index;
            private boolean beforeIndex = false; //Start with `false` therefore if it is removed from or added to the list it still works

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

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

            @Override
            public void exportDone(JComponent comp, Transferable trans, int action) {
                if (action == MOVE) {
                    if(beforeIndex)
                        strings.remove(index + 1);
                    else
                        strings.remove(index);
                }
            }

            @Override
            public boolean canImport(TransferHandler.TransferSupport support) {
                return support.isDataFlavorSupported(DataFlavor.stringFlavor);
            }

            @Override
            public boolean importData(TransferHandler.TransferSupport support) {
                try {
                    String s = (String) support.getTransferable().getTransferData(DataFlavor.stringFlavor);
                    JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
                    strings.add(dl.getIndex(), s);
                    beforeIndex = dl.getIndex() < index ? true : false;
                    return true;
                } catch (UnsupportedFlavorException | IOException e) {
                    e.printStackTrace();
                }

                return false;
            }
        });

        JScrollPane scrollPane = new JScrollPane(dndList);
        getContentPane().add(scrollPane);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> new GUI());
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!