-1

I've got a couple of JList both being able to be sources and destinations of DnD COPY actions. They work fine, but for one thing - elements are added at the bottom of the lists, instead at the drop destination row, as I'd like.

Since Oracle BasicDnD example includes this feature (and, in fact, it's the only application I've been able to find through Google with it), I've been taking a look at its source to see if I was able to adapt it. I tried to set the TransferHandler object, which is what I guess I'm missing, but no new behavior appeared. So, what (else) may I be missing/doing wrong?

EDIT1: Below is shown the class I use for these lists.

private class InteractiveJList extends JList implements DragGestureListener, 
DragSourceListener, DropTargetListener {

private final DropTarget dropTarget;
private final DragSource dragSource;
private final boolean    removeElementsOnFail;
private int[]            selectedOnes;

@SuppressWarnings("unchecked")
private InteractiveJList(final ListModel model, 
final boolean _removElementsOnFail) {
    super(model);
    this.dragSource = new DragSource();
    this.dragSource
        .createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY,
this);
this.dropTarget = new DropTarget(this, this);
this.removeElementsOnFail = _removElementsOnFail;
}

@Override
public void dragEnter(final DropTargetDragEvent arg0) {
}

@Override
public void dragExit(final DropTargetEvent arg0) {
}

@Override
public void dragOver(final DropTargetDragEvent arg0) {
}

@Override
public void drop(final DropTargetDropEvent event) {
    if (!this.removeElementsOnFail) {
    event.rejectDrop();
    return;
    }
    final Transferable transferable = event.getTransferable();

    if (transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) {
    String all;
    try {
        all = (String) transferable.getTransferData(DataFlavor.stringFlavor);
    } catch (final UnsupportedFlavorException | IOException ex) {
        event.rejectDrop();
        return;
    }
    event.acceptDrop(DnDConstants.ACTION_COPY);
    final StringTokenizer tokenizer = new StringTokenizer(all, "|");
    while (tokenizer.hasMoreTokens()) {
        ((StringListModel) this.getModel()).addElement(tokenizer.nextToken());
    }
    event.getDropTargetContext().dropComplete(Boolean.TRUE);
    } else {
    event.rejectDrop();
    }
}

@Override
public void dropActionChanged(final DropTargetDragEvent event) {
}

@Override
public void dragEnter(final DragSourceDragEvent arg0) {
}

@Override
public void dragExit(final DragSourceEvent arg0) {
}

@Override
public void dragOver(final DragSourceDragEvent arg0) {
}

@Override
public void dropActionChanged(final DragSourceDragEvent dragSourceEvent) {
}

@Override
public void dragGestureRecognized(final DragGestureEvent dragGestureEvent) {
    final Object listSelectedValue = this.getSelectedValue();
    this.selectedOnes = this.getSelectedIndices();
    final StringBuilder bridge = new StringBuilder();
    for (final int x : this.selectedOnes) {
    bridge.append(this.getModel().getElementAt(x)).append("|");
    }
    if (listSelectedValue != null) {
    final StringSelection stringTransferable =
new StringSelection(bridge.toString()
            .substring(0, bridge.length() - 1));
    this.dragSource.startDrag(dragGestureEvent, DragSource.DefaultCopyDrop,
            stringTransferable, this);
    }
}

@Override
public void dragDropEnd(final DragSourceDropEvent dragSourceDropEvent) {
    if (!dragSourceDropEvent.getDropSuccess()) {
    if (this.removeElementsOnFail) {
        for (final int x : this.selectedOnes) {
        ((StringListModel) this.getModel()).removeElement(x);
        }
    }
    }
}

}

The boolean flag in the constructor is used because, for one of the lists, if the drop action if rejected, the element must be removed, but not in the another. This way I can handle that. StringListModel is an extension of AbstractListModel to handle list of Strings. It is able to add elements both at bottom and at a requested position, clears the list, and remove certain elements. It does its stuff, fires the corresponding events and nothing further.

EDIT2: Below is shown my implementation of MadProgrammer's suggestion:

   private static class UserTransferHandler extends TransferHandler {

private final JList list;

private UserTransferHandler(final JList list) {
    this.list = list;
}

@Override
public boolean canImport(final TransferSupport support) {
    System.out.println("canImport");
    boolean canImport = false;
    if (support.isDataFlavorSupported(UserTransferable.USER_DATA_FLAVOR)) {
    final JList.DropLocation dl = 
(JList.DropLocation) support.getDropLocation();
    if (dl.getIndex() != -1) {
        canImport = true;
    }
    }
    return canImport;
}

@Override
protected void exportDone(final JComponent source, final Transferable data,
final int action) {
    System.out.println("exportDone");
}

@Override
public boolean importData(final TransferSupport support) {
    System.out.println("importData");
    boolean accepted = false;

    if (support.isDrop()) {
    if (support.isDataFlavorSupported(UserTransferable.USER_DATA_FLAVOR)) {

        final JList.DropLocation dl = 
(JList.DropLocation) support.getDropLocation();
        final StringListModel model = (StringListModel) this.list.getModel();
        final int index = dl.getIndex();
        final boolean insert = dl.isInsert();

        final Transferable t = support.getTransferable();
        try {
        final String dropped = (String) t
                .getTransferData(UserTransferable.USER_DATA_FLAVOR);
        if (insert) {
            if (index >= model.getSize()) {
            model.addElement(dropped);
            } else {
            model.addElementAt(dropped, index);
            }
        } else {
            model.addElement(dropped);
        }

        accepted = true;
        } catch (final Exception e) {
        e.printStackTrace();
        }
    }
    }
    return accepted;
}

@Override
public int getSourceActions(final JComponent c) {
    System.out.println("getSourceActions");
    return TransferHandler.COPY_OR_MOVE;
}

@Override
protected Transferable createTransferable(final JComponent c) {
    System.out.println("createTransferable");
    final String value = this.list.getSelectedValue().toString();
    return new UserTransferable(value);
}

}

private static class UserTransferable implements Transferable {

private static final DataFlavor USER_DATA_FLAVOR = 
new DataFlavor(String.class, "String");

private final String            value;

private UserTransferable(final String value) {
    System.out.println("UserTransferable");
    this.value = value;
}

@Override
public DataFlavor[] getTransferDataFlavors() {
    System.out.println("getTransferDataFlavors");
    return new DataFlavor[] { UserTransferable.USER_DATA_FLAVOR };
}

@Override
public boolean isDataFlavorSupported(final DataFlavor flavor) {
    System.out.println("isDataFlavorSupported");
    return UserTransferable.USER_DATA_FLAVOR.equals(flavor);
}

@Override
public Object getTransferData(final DataFlavor flavor) throws 
UnsupportedFlavorException, IOException {
    System.out.println("getTransferData");
    if (!UserTransferable.USER_DATA_FLAVOR.equals(flavor)) {
    throw new UnsupportedFlavorException(flavor);
    }
    return this.value;
}

}

The print statements are there to get feedback when the methods are called (which is, right now, never).

  • Show us the you are using to start with. I'd also check your drop mode – MadProgrammer Mar 20 '13 at 19:25
  • @MadProgrammer Sorry I don't like to put code that I don't see indispensable in order to ease reading and I wasn't sure it could be needed (first time with DnD). Edited the post to add the code and a brief explanation. – Jorge Antonio Díaz-Benito Mar 20 '13 at 20:03
  • 2
    The problem, as I see it, is yr mixing two parts of the D'n'D API. Your using the low level API in your code and excepting it to work like the newer component based API. You can take a look at [this](http://stackoverflow.com/questions/15520610/java-drag-and-drop/15521421#15521421) question which uses the low level API for a basic example. I the drop target listener, it is possible to ascertain the location that the drop occurred, you'd have to determine at which row this point translates to and insert your data at that point – MadProgrammer Mar 20 '13 at 20:42
  • mm..I was guessing I could do this, but since I had read the transferhandler stuff, I was expecting a more "beautiful", "proper" way to do it. But well, I'll have to go for coords then. Thanks. – Jorge Antonio Díaz-Benito Mar 20 '13 at 20:56
  • 1
    D'n'D is extraordinary complex and the D'n'D API is actually very good at hiding much of that complexity. What you need to do is be careful about mixing the low level API with the higher level component API – MadProgrammer Mar 20 '13 at 22:31
  • Please put a bit more effort in formatting the code (note that the formatter doesn't play nicely with tabs), as is, it borders on being unreadable – kleopatra Mar 21 '13 at 12:22
  • don't quite understand what exactly is not working (using @Mad's code) as you expect it? – kleopatra Mar 21 '13 at 12:26
  • @kleopatra No problem, just explain me what you mean and I'll do it - the formatter doesn't play nicely with tabs? – Jorge Antonio Díaz-Benito Mar 21 '13 at 12:27
  • @kleopatra There's no new behavior. Elemets keep being added at the beginning of the target list, not where they're dropped. – Jorge Antonio Díaz-Benito Mar 21 '13 at 12:28
  • as to the formatting: you see that the result is ... suboptimal with those visually ragged indents? As the alignment looks fine in edit mode, I suspect you have tab chars in the code. Replace them by spaces most probably will clean up the mess. As the the not working: than its time for an SSCCE (please google if you are not familiar with the abbr.) that demonstrates the actual vs. the intended behaviour. After all, @Mad's code is working as I expect it, so either my expectation doesn't match yours or there must be something in your code :-) – kleopatra Mar 21 '13 at 12:36
  • @kleopatra If I didn't misunderstand you, the code should now look fine. As for a SSCE, I can't provide one of what I'm intending to do, becase that's why I'm asking, I don't know how to implement it. Such intended behavior is: if the user drops the dragged item over an already existing item in the destination JList (let's call this item A), the dropped element should be added between A and its previous item (or at the beginning of the list if A is at index 0). – Jorge Antonio Díaz-Benito Mar 21 '13 at 13:02
  • @kleopatra cont. The closest thing to a SSCCE I can provide is already in the answer. If you run [the provided .jnlp file](http://docs.oracle.com/javase/tutorialJWS/uiswing/dnd/ex6/BasicDnD.jnlp) and turn on the drag-and-drop mode (left-bottom corner), you'll see you can drag elements from the JList into the JTextArea, and they'll be added at the end of the JTextArea, no matter where in it you drop them. But, if you make the reverse process, you'll get a dialog reporting the place where the element has been dropped, and if I could access to such information in the code, there'd be no problem. – Jorge Antonio Díaz-Benito Mar 21 '13 at 13:05
  • 1
    You've not provided the code for how you set the lists up, make sure you're using setDropMode(DropMode.ON_OR_INSERT); – MadProgrammer Mar 21 '13 at 20:03

1 Answers1

3

This is a fully working example that uses the (newer) Transferable API. This is based on the example you have linked.

Note, I've not bothered with using Strings, as this just confuses people, instead, I'm transferring objects.

Also note, that I'm using a "move" API, so names will be moved from one list to the other...

enter image description here

import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;

public class ComponentAPIDnD {

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

    public ComponentAPIDnD() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JList left;
        private JList right;

        public TestPane() {
            setLayout(new GridLayout(0, 2));
            DefaultListModel<User> model = new DefaultListModel<>();
            model.addElement(new User("Rai"));
            model.addElement(new User("Mark"));
            model.addElement(new User("Han"));
            model.addElement(new User("Luke"));
            model.addElement(new User("Ben"));
            model.addElement(new User("Yoda"));
            left = new JList(model);
            left.setCellRenderer(new UserListCellRenderer());
            right = new JList(new DefaultListModel<User>());
            right.setCellRenderer(new UserListCellRenderer());

            left.setName("Left");
            right.setName("Right");

            add(new JScrollPane(left));
            add(new JScrollPane(right));

            left.setTransferHandler(new UserTransferHandler(left));
            right.setTransferHandler(new UserTransferHandler(right));

            left.setDropMode(DropMode.ON_OR_INSERT);
            right.setDropMode(DropMode.ON_OR_INSERT);

            left.setDragEnabled(true);
            right.setDragEnabled(true);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }
    }

    public static class UserTransferHandler extends TransferHandler {

        private JList list;

        public UserTransferHandler(JList list) {
            this.list = list;
        }

        @Override
        public boolean canImport(TransferSupport support) {
            boolean canImport = false;
            if (support.isDataFlavorSupported(UserTransferable.USER_DATA_FLAVOR)) {
                JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
                if (dl.getIndex() != -1) {
                    canImport = true;
                }
            }
            return canImport;
        }

        @Override
        protected void exportDone(JComponent source, Transferable data, int action) {
            try {
                User user = (User) data.getTransferData(UserTransferable.USER_DATA_FLAVOR);
                ((DefaultListModel<User>)list.getModel()).removeElement(user);
            } catch (UnsupportedFlavorException | IOException ex) {
                ex.printStackTrace();
            }
        }

        @Override
        public boolean importData(TransferSupport support) {
            boolean accepted = false;
            if (support.isDrop()) {
                if (support.isDataFlavorSupported(UserTransferable.USER_DATA_FLAVOR)) {

                    JList.DropLocation dl = (JList.DropLocation)support.getDropLocation();
                    DefaultListModel<User> model = (DefaultListModel<User>) list.getModel();
                    int index = dl.getIndex();
                    boolean insert = dl.isInsert();

                    Transferable t = support.getTransferable();
                    try {
                        User dropped = (User) t.getTransferData(UserTransferable.USER_DATA_FLAVOR);
                        System.out.println("Drop " + dropped + " on " + list.getName());
                        if (insert) {
                            if (index >= model.getSize()) {
                                model.addElement(dropped);
                            } else {
                                model.add(index, dropped);
                            }
                        } else {
                            model.addElement(dropped);
                        }

                        accepted = true;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            return accepted;
        }

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

        @Override
        protected Transferable createTransferable(JComponent c) {
            User user = (User) list.getSelectedValue();
            return new UserTransferable(user);
        }

    }

    public static class UserTransferable implements Transferable {

        public static final DataFlavor USER_DATA_FLAVOR = new DataFlavor(User.class, "User");

        private User user;

        public UserTransferable(User user) {
            this.user = user;
        }

        @Override
        public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[] {USER_DATA_FLAVOR};
        }

        @Override
        public boolean isDataFlavorSupported(DataFlavor flavor) {
            return USER_DATA_FLAVOR.equals(flavor);
        }

        @Override
        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
            Object value = null;
            if (USER_DATA_FLAVOR.equals(flavor)) {
                value = user;
            } else {
                throw new UnsupportedFlavorException(flavor);
            }
            return user;
        }

    }

    public class User {

        private String name;

        public User(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    public class UserListCellRenderer extends DefaultListCellRenderer {

        @Override
        public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);

            if (value instanceof User) {

                setText(((User) value).getName());

            }

            return this;
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Looks fine, I'll try to implement it tomorrow as soon as I wake up. Just one thing more - I can't find how you setup the MOVE stuff. I guess it's already done (like default behavior or so)? How would it be modified into COPY? I can't believe something looking so easy is actually so complex and has so many ways to work... – Jorge Antonio Díaz-Benito Mar 20 '13 at 23:30
  • The `exportDone` method of the `UserTransferHandler` class actually creates the illusion of the move. If you were to comment this out, you would end up with a shallow copy (the same instance of the same object would exist in both lists) – MadProgrammer Mar 20 '13 at 23:38
  • It's not working for me yet. Although the constructor is called in the calls to setTransferHandler, all other methods from `UserTransferHandler` and `UserTransferable` are not called, never. The behavior is the same as in the beginning. I'm about to edit my question to include my implementation of your suggestion. – Jorge Antonio Díaz-Benito Mar 21 '13 at 11:39
  • 1
    Copy the above example, run it unmodified. Once you can see it working, play with some other parts of the code, add in System.outs where you think it's appropriate to track the work flow. Try a few different settings to see what they effect – MadProgrammer Mar 21 '13 at 20:09
  • Although it's true I had only placed the transferHandlers but forgot about the dropMode, nothing changes when I put it. I've also tried with the raw source example, but still no new behavior appears. I think I'm going to give up on this, I don't want to cause further disturbance. Thanks anyway. – Jorge Antonio Díaz-Benito Mar 22 '13 at 11:07