Drag and Drop custom object from JList into JLabel

Andy Levesque picture Andy Levesque · Dec 13, 2012 · Viewed 7.1k times · Source

I have a JList containing an ArrayList of custom objects and I'm trying to create a drag and drop into fields. I'm having trouble understanding how to package and receive the object in Transferable.

This is about as far as I've gotten:

import java.awt.*;

import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;

public class FlightProjectInterface extends JFrame{

    //create GUI Objects

    private JFrame primaryFrame;
    private JPanel createFlightPanel;
    private JPanel aircraftLayout;

    private JList personsJList, personsOnFlightJList;
    private JTextField pilotLabel, coPilotLabel, backseat1Label, backseat2Label;

    public FlightProjectInterface(){

        //establish frame
        super("Create Flight");
        setLayout( new FlowLayout());

        //aircraftPanel
        aircraftLayout = new JPanel();
        aircraftLayout.setLayout(new GridLayout(2,2));
        pilotLabel = new JTextField("Drag Pilot Here");

        //build person load list
        DefaultListModel listModel = new DefaultListModel();
        for (Person person : Database.persons)
            listModel.addElement(person);

        personsJList = new JList(listModel);
        personsJList.setVisibleRowCount(5);
        personsJList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        personsJList.setDragEnabled(true);

        add( new JScrollPane(personsJList) );

        aircraftLayout.add(pilotLabel);
        add(aircraftLayout);

    }//end constructor

}

Clarification: I'm having trouble taking the object selection from the JList and creating a Transferable out of it. With the code above, the toString representation of the object is simply pasted in the text field, so I'm not able to pull object data from the dropped location. How can I "package" the object itself and drop it into a placeholder that I can reference the object itself from the GUI?

Ideally, there would be 4 fields that each contains an object that can be dropped. The person would be removed from the list if they are dropped, but returned to the list if replaced.

Answer

MadProgrammer picture MadProgrammer · Dec 13, 2012

Drag and Drop can be a complex beast, not made any easier by the conflicting information that's available. Personally, I like to avoid the Transfer API, but I'm old school like that.

The glue to DnD really is the DataFlavor. I prefer to roll my own, makes life a lot easier.

In this example, I've used a single TransferHandler, but realistically, you really should have one for dragging and one for dropping, in particular, you should have one for each component you want to drop onto.

The main reason for this is, I put a trap in my canImport method to reject it if your dragging over a JList, so you can only drop it on the JLabel, this is a little hack and probably not the best idea.

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.io.IOException;

import javax.swing.DefaultListModel;
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.TransferHandler;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class DnDTransferableTest {

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

    public DnDTransferableTest() {
        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);
            }
        });
    }

    @SuppressWarnings("serial")
    public class TestPane extends JPanel {

        private JList<ListItem> list;
        private JLabel label;

        public TestPane() {

            list = new JList<ListItem>();
            list.setDragEnabled(true);
            list.setTransferHandler(new ListTransferHandler());

            DefaultListModel<ListItem> model = new DefaultListModel<ListItem>();
            for (int index = 0; index < 10; index++) {

                model.addElement(new ListItem("Item " + index));

            }
            list.setModel(model);

            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.weighty = 1;
            gbc.weightx = 1;
            gbc.fill = GridBagConstraints.BOTH;
            add(new JScrollPane(list), gbc);

            label = new JLabel("Drag on me...");
            gbc.gridx++;
            gbc.weightx = 1;
            gbc.fill = GridBagConstraints.NONE;
            add(label, gbc);

            label.setTransferHandler(new ListTransferHandler());

        }
    }

    @SuppressWarnings("serial")
    public class ListTransferHandler extends TransferHandler {

        @Override
        public boolean canImport(TransferSupport support) {
            return (support.getComponent() instanceof JLabel) && support.isDataFlavorSupported(ListItemTransferable.LIST_ITEM_DATA_FLAVOR);
        }

        @Override
        public boolean importData(TransferSupport support) {
            boolean accept = false;
            if (canImport(support)) {
                try {
                    Transferable t = support.getTransferable();
                    Object value = t.getTransferData(ListItemTransferable.LIST_ITEM_DATA_FLAVOR);
                    if (value instanceof ListItem) {
                        Component component = support.getComponent();
                        if (component instanceof JLabel) {
                            ((JLabel)component).setText(((ListItem)value).getText());
                            accept = true;
                        }
                    }
                } catch (Exception exp) {
                    exp.printStackTrace();
                }
            }
            return accept;
        }

        @Override
        public int getSourceActions(JComponent c) {
            return DnDConstants.ACTION_COPY_OR_MOVE;
        }

        @Override
        protected Transferable createTransferable(JComponent c) {
            Transferable t = null;
            if (c instanceof JList) {
                @SuppressWarnings("unchecked")
                JList<ListItem> list = (JList<ListItem>) c;
                Object value = list.getSelectedValue();
                if (value instanceof ListItem) {
                    ListItem li = (ListItem) value;
                    t = new ListItemTransferable(li);
                }
            }
            return t;
        }

        @Override
        protected void exportDone(JComponent source, Transferable data, int action) {
            System.out.println("ExportDone");
            // Here you need to decide how to handle the completion of the transfer,
            // should you remove the item from the list or not...
        }
    }

    public static class ListItemTransferable implements Transferable {

        public static final DataFlavor LIST_ITEM_DATA_FLAVOR = new DataFlavor(ListItem.class, "java/ListItem");
        private ListItem listItem;

        public ListItemTransferable(ListItem listItem) {
            this.listItem = listItem;
        }

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

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

        @Override
        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {

            return listItem;

        }
    }

    public static class ListItem {

        private String text;

        public ListItem(String text) {
            this.text = text;
        }

        public String getText() {
            return text;
        }

        @Override
        public String toString() {
            return getText();
        }
    }
}