Search code examples
javaswingdrag-and-dropjlabel

Move and Reorder JLabels in JPanel using DnD


I have a program I'm working on that takes input names and creates JLabels and populates them into an ArrayList. When the user clicks the Begin button, the JLabels populate in the left JPanel. What I've been trying to do it use DnD to allow the user to drag and re-order the labels in the left panel. So far, I've only been able to get it to copy the text from one label to another. My goal is to drag a label up or down in the list, and depending on where it's dropped in the JPanel, have the remaining JLabels move up or down in the panel. I'm using a FlowLayout for the panel, and the labels are wide enough to create vertical alignment. Here is my code:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.TransferHandler;
import javax.swing.border.Border;


public class Gui extends JFrame {
/**
 * 
 */
private static final long serialVersionUID = 1L;
private ArrayList<JLabel> players = new ArrayList<JLabel>();
private ArrayList<JLabel> monsters = new ArrayList<JLabel>();
private JButton addPlayer = new JButton();
private JButton removePlayer = new JButton();
private JButton addMonster = new JButton();
private JButton removeMonster = new JButton();
private JTextField name = new JTextField();
private JPanel listPanel = new JPanel();
private JPanel buttonPanel = new JPanel();
private JButton encounter = new JButton();
private JButton begin = new JButton();
//private JButton remove;// = new JButton();
int x_pressed;
int y_pressed;


public Gui(){
    super("D&D Initiative");
    final Container container = getContentPane();
    container.setLayout(new FlowLayout()); //change to GridBagLayout
    container.setPreferredSize(new Dimension(800, 500));

    listPanel.setPreferredSize(new Dimension(600, 500));
    listPanel.setBackground(Color.BLUE);
    listPanel.setOpaque(true);
    buttonPanel.setPreferredSize(new Dimension(200,500));
    buttonPanel.setBackground(Color.GREEN);
    buttonPanel.setOpaque(true);

    Dimension size = new Dimension(190,30);

    name.setPreferredSize(new Dimension(190,30));
    addPlayer.setText("Add Player");
    removePlayer.setText("Remove Player");
    addPlayer.setPreferredSize(size);
    removePlayer.setPreferredSize(size);
    addMonster.setText("Add Monster");
    removeMonster.setText("Remove Monster");
    addMonster.setPreferredSize(size);
    removeMonster.setPreferredSize(size);
    begin.setText("Begin Encounter");
    begin.setPreferredSize(size);

    buttonPanel.add(name);
    buttonPanel.add(addPlayer);
    buttonPanel.add(addMonster);
    buttonPanel.add(removePlayer);
    buttonPanel.add(removeMonster);
    buttonPanel.add(begin);







    final Border paddingBorder = BorderFactory.createEmptyBorder(0,10,0,0);

    addPlayer.addActionListener(new ActionListener(){
        public void actionPerformed(ActionEvent e){
            if(name.getText().compareToIgnoreCase("") != 0){
            final JLabel temp=new JLabel();
            temp.setPreferredSize(new Dimension(590,50));
            temp.setText(name.getText());
            temp.setBackground(Color.GRAY);
            temp.setForeground(Color.WHITE);
            temp.setOpaque(true);
            temp.setLayout(new BorderLayout());
            temp.setBorder(BorderFactory.createCompoundBorder(null,paddingBorder));
            //bPane.setPreferredSize(new Dimension(100,temp.getHeight()));

            JButton remove = new JButton();
            remove.setText("Remove");
            remove.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent e){
                    System.out.println("Count of listeners: " + ((JButton) e.getSource()).getActionListeners().length); //debugging
                    listPanel.remove(temp);
                    listPanel.revalidate();
                    System.out.println("remove");
                    listPanel.repaint();
                }
            });
            temp.add(remove, BorderLayout.EAST);


            MouseListener listener = new DragMouseAdapter();
            temp.setTransferHandler(new TransferHandler("text"));
            temp.addMouseListener(listener);



            players.add(temp);
            name.setText("");
            name.requestFocusInWindow();
            }else{
                //throw error
            }
        }
    });

    begin.addActionListener(new ActionListener(){
        public void actionPerformed(ActionEvent e){
            for(int i=0;i<players.size();i++){
                JLabel h = players.get(i);
                h.setMinimumSize(new Dimension(590,30));
                int s = container.getHeight()/players.size();
                if(s < 50){
                    h.setPreferredSize(new Dimension(590,listPanel.getHeight()/(players.size()+1)));
                }else{
                    h.setPreferredSize(new Dimension(590,30));
                }
                listPanel.add(h, new Integer(1), 0);
            }
            listPanel.revalidate();
        }
    });

    container.add(listPanel);
    container.add(buttonPanel);

    pack();     // pack the frame to fit the container inside
    setLocation(150, 150);  // set the location on the monitor
    setVisible(true);
    setResizable(false);

}


/**
 * @param args
 */
public static void main(String[] args) {
    // TODO Auto-generated method stub
    Gui application = new Gui();
    application.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

}

//start of drag and drop

class DragMouseAdapter extends MouseAdapter {
  public void mousePressed(MouseEvent e) {
    JComponent c = (JComponent) e.getSource();
    TransferHandler handler = c.getTransferHandler();
    handler.exportAsDrag(c, e, TransferHandler.COPY);
    System.out.println(c);
  }
}

There are a few things in there still from different attempts (such as unused variables).


Solution

  • Consider using a JList rather than creating you own component of labels, this has a lot of neat functionality that will just make your life simpler, like drag and drop...

    Have a look at How to Use Lists for more details.

    Drag and Drop is quite a complex API and can be quite confusing to begin with, which isn't helped by the inclusion of the "Transfer API" which just adds to the confusion.

    Having said that, the "Transfer API" is exactly what you're look for in this case...

    Swapping

    import java.awt.EventQueue;
    import java.awt.datatransfer.DataFlavor;
    import java.awt.datatransfer.Transferable;
    import java.awt.datatransfer.UnsupportedFlavorException;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    import javax.activation.ActivationDataFlavor;
    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.TransferHandler;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class DragList {
    
        public static void main(String[] args) {
            new DragList();
        }
    
        public DragList() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    DefaultListModel<String> listModel = new DefaultListModel<>();
                    listModel.addElement("Clara    Holmes");
                    listModel.addElement("Bill    Moss");
                    listModel.addElement("Della    Reeves");
                    listModel.addElement("Lloyd    Gross");
                    listModel.addElement("Cecilia    Floyd");
                    listModel.addElement("Delia    Cummings");
                    listModel.addElement("Tommy    Benson");
                    listModel.addElement("Kirk    Casey");
                    listModel.addElement("Chester    Manning");
                    listModel.addElement("Elsa    Chapman");
                    JList namesList = new JList(listModel);
                    namesList.setDragEnabled(true);
                    namesList.setDropMode(DropMode.ON_OR_INSERT);
                    namesList.setTransferHandler(new ListItemTransferHandler());
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new JScrollPane(namesList));
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class ListItemTransferHandler extends TransferHandler {
    
            private DataFlavor flavor;
    
            public ListItemTransferHandler() {
                super("ListMove");
                flavor = new ActivationDataFlavor(int[].class, DataFlavor.javaJVMLocalObjectMimeType, "List of values");
            }
    
            @Override
            protected Transferable createTransferable(JComponent c) {
    
                System.out.println("createTransferable");
                JList list = (JList) c;
                DefaultListModel<String> model = (DefaultListModel<String>) list.getModel();
                int[] selectedValues = list.getSelectedIndices();
    
                List<ValuePair> values = new ArrayList<>(selectedValues.length);
                for (int index : selectedValues) {
                    values.add(new ValuePair(index, model.getElementAt(index)));
                }
    
                return new ListOfValues(values);
            }
    
            @Override
            public boolean canImport(TransferSupport support) {
                boolean canImport = support.isDrop() && support.isDataFlavorSupported(flavor);
                return canImport;
            }
    
            @Override
            public int getSourceActions(JComponent c) {
                return MOVE;
            }
    
            @Override
            public boolean importData(TransferSupport support) {
    
                boolean imported = false;
                if (canImport(support)) {
    
                    JList target = (JList) support.getComponent();
                    target.clearSelection();
                    JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
                    DefaultListModel<String> model = (DefaultListModel<String>) target.getModel();
                    int addIndex = dl.getIndex();
                    if (addIndex < 0 || addIndex >= model.size()) {
                        addIndex = model.size();
                    }
    
                    try {
                        List<ValuePair> values = (List<ValuePair>) support.getTransferable().getTransferData(flavor);
                        for (ValuePair vp : values) {
                            int index = vp.getIndex();
                            String value = model.get(index);
                            model.add(addIndex, value);
                            target.addSelectionInterval(addIndex, addIndex);
                            addIndex++;
                        }
                        imported = true;
                    } catch (UnsupportedFlavorException | IOException ex) {
                        ex.printStackTrace();
                    }
    
                }
    
                return imported;
    
            }
    
            @Override
            protected void exportDone(JComponent source, Transferable data, int action) {
                if (action == MOVE) {
    
                    JList list = (JList) source;
                    DefaultListModel<String> model = (DefaultListModel<String>) list.getModel();
                    try {
    
                        // Get the list of values and sort in decending order
                        List<ValuePair> values = (List<ValuePair>) data.getTransferData(flavor);
                        Collections.sort(values, new Comparator<ValuePair>() {
                            @Override
                            public int compare(ValuePair o1, ValuePair o2) {
                                return o2.getIndex() - o1.getIndex();
                            }
                        });
    
                        // Loop through for each item...
                        for (ValuePair vp : values) {
    
                            // Get the "last known" index of the value...
                            String modelValue = model.get(vp.getIndex());
                            String value = vp.getValue();
                            int index = vp.getIndex();
    
                            // If the value no long resides at it's last location, we need to find it,
                            // we can't rely on contains, as there are two values of the same value...
                            if (modelValue != value) {
    
                                // Look behind us, only the length of the number of values that were previously selected...
                                int startIndex = Math.max(vp.getIndex() - values.size(), 0);
                                int endIndex = vp.getIndex();
                                index = getIndexFor(value, model, startIndex, endIndex);
                                if (index < 0) {
    
                                    // Look forward, only the length of the number of values that were previously selected...
                                    endIndex = Math.min(vp.getIndex() + values.size(), model.size() - 1);
                                    startIndex = vp.getIndex();
                                    index = getIndexFor(value, model, startIndex, endIndex);
    
                                }
    
                            }
    
                            if (index < 0) {
    
                                // We lost the item ??
                                throw new IOException("Missing value?");
    
                            } else {
    
                                model.remove(index);
    
                            }
    
                        }
    
                    } catch (UnsupportedFlavorException | IOException ex) {
                        ex.printStackTrace();
                    }
    
                }
            }
    
            protected int getIndexFor(String value, DefaultListModel<String> model, int startIndex, int endIndex) {
    
                while (model.get(startIndex) != value && startIndex <= endIndex) {
                    startIndex++;
                }
    
                return model.get(startIndex) == value ? startIndex : -1;
    
            }
    
            public class ValuePair {
    
                private int index;
                private String value;
    
                public ValuePair(int index, String value) {
                    this.index = index;
                    this.value = value;
                }
    
                public int getIndex() {
                    return index;
                }
    
                public String getValue() {
                    return value;
                }
    
            }
    
            public class ListOfValues implements Transferable {
    
                private List<ValuePair> values;
    
                public ListOfValues(List<ValuePair> values) {
                    this.values = values;
                }
    
                @Override
                public DataFlavor[] getTransferDataFlavors() {
                    return new DataFlavor[]{flavor};
                }
    
                @Override
                public boolean isDataFlavorSupported(DataFlavor flavor) {
                    return Arrays.asList(getTransferDataFlavors()).contains(flavor);
                }
    
                @Override
                public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
                    return values;
                }
    
            }
    
        }
    
    }
    

    Take a look at Demo - BasicDnD for another demo as well as more information on the transfer API in general.