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.
The selection mode can be changed on the selection model directly, or via JList 's cover method.
We can display a value when an item is selected from a JList by implementing MouseListener interface or extending MouseAdapter class and call the getClickCount() method with single-click event (getClickCount() == 1) of MouseEvent class.
to determine which item is selected, use the getSelectedIndex() method of the JList. This returns the index of the item selected by the user, or -1 if no item is selected.
getSelectedValuesList() returns a list of all the selected items.
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());
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With