Drag and Drop Custom Object from Jlist into Jlabel

Drag and Drop custom object from JList into JLabel

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

How to drag and drop a JLabel into a JPanel

After looking at a few examples posted by @MadProgrammer I came up with a solution that extends both the JPanel and JLabel. Here is the JLabel class:

public class LayerItem extends JLabel {

public LayerItem(String text) {

this.setText(text);

this.setTransferHandler(new ValueExportTransferHandler(text));

this.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
JLabel lbl = (JLabel) e.getSource();
TransferHandler handle = lbl.getTransferHandler();
handle.exportAsDrag(lbl, e, TransferHandler.COPY);
}
});

}

protected static class ValueExportTransferHandler extends TransferHandler {

public static final DataFlavor SUPPORTED_DATE_FLAVOR = DataFlavor.stringFlavor;
private String value;

public ValueExportTransferHandler(String value) {
this.value = value;
}

public String getValue() {
return value;
}

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

@Override
protected Transferable createTransferable(JComponent c) {
Transferable t = new StringSelection(getValue());
return t;
}

@Override
protected void exportDone(JComponent source, Transferable data, int action) {
super.exportDone(source, data, action);
// Clean up and remove the LayerItem that was moved
((LayerItem) source).setVisible(false);
((LayerItem) source).getParent().remove((LayerItem) source);
}

}
}

Here is the class for the JPanel:

public class LayerContainer extends JPanel {

public LayerContainer() {
this.setTransferHandler(new ValueImportTransferHandler());
this.setLayout(new GridBagLayout()); // Optional layout
this.setBorder(new CompoundBorder(new LineBorder(Color.DARK_GRAY), new EmptyBorder(20, 20, 20, 20))); // Optional border
}

protected static class ValueImportTransferHandler extends TransferHandler {

public static final DataFlavor SUPPORTED_DATE_FLAVOR = DataFlavor.stringFlavor;

public ValueImportTransferHandler() {
}

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

@Override
public boolean importData(TransferHandler.TransferSupport support) {
boolean accept = false;
if (canImport(support)) {
try {
Transferable t = support.getTransferable();
Object value = t.getTransferData(SUPPORTED_DATE_FLAVOR);
if (value instanceof String) { // Ensure no errors
// TODO: here you can create your own handler
// ie: ((LayerContainer) component).getHandler()....
Component component = support.getComponent();
LayerItem j = new LayerItem((String) value);
((LayerContainer) component).add(j); // Add a new drag JLabel
accept = true;
}
} catch (Exception exp) {
exp.printStackTrace();
}
}
return accept;
}
}

}

Here is an example of how you could use this (drag from one JPanel to another and back again):

    JPanel left_panel = new LayerContainer();
panel_1.setBounds(28, 47, 129, 97);
add(panel_1);

LayerContainer right_panel = new LayerContainer();
layerContainer.setBounds(203, 47, 129, 97);
add(layerContainer);

JLabel lblToDrag = new LayerItem("Drag Me");
GridBagConstraints gbc_lblToDrag = new GridBagConstraints();
gbc_lblDragMe.gridx = 0;
gbc_lblDragMe.gridy = 0;
panel_right.add(lblToDrag, gbc_lblToDrag);

For future use, I'll create a onTransfer() method and create my own LayerContainerHandler() which overrites a run() method so each time a Label is moved to different Containers, it execute seperate actions.

Store custom object in JLabel on event

You have a number of choices that I can see

Choice #1

Create a Map of some kind and store the object against the label, using the label as it's key

Map<JLabel, Person> mapPeople = new HashMap<JLable, Person>(25)

Then in your import method

mapPeople.put((JLabel)component, (Person)value);

Choice #2

Take advantage of the put/getClientProperty to store the value against a named key...

In your import method...

((JLabel)component).putClientProperty("person", value);

And when you need it again

Person person = (Person)label.getClientProperty("person");

Choice #3

Create a custom label that is capable of referencing the person directly.

public class PersonLabel extends JLabel {
private Person person;
public void setPerson(Person person) {
this.person = person;
setText(person == null ? null : person.toString());
}

public Person getPerson() {
return person;
}
}

Basically, you would use this component in place of the normal JLabel, make sure you cast it correctly.

Credit to Hovercraft and trashgod for mentioning most of this first.

Final Choice

The final choice will come down to your own design, requirements and how re-usable you want the solution to be.

If this is a "one" off requirement for your application, then the Map and client properties are reasonable solutions.

The Map is more highly visible to other developers, it obvious what you are trying to do. It is, however, easy to mess up. Having the map either contain irrelevant data or have missing data because some one forgot to/or didn't know to follow the requirements.

The clientProperty does solve some the short comings of the Map in this sense, as you a re dealing with a single component, not a component and a Map (although it's still possible for developers to forget to set the property) but has a much lower visibility when compared to the Map. It might take longer for developers to realise how you are storing/retrieving the data.

It does, how ever, have the advantage of been simple. You can pass a reference to the label and you have not just the label, but also the Person as well.

The custom label provides you the opportunity for flexibility. It's more obvious then the clientProperty solution, as it has defined public methods. It also allows you the opportunity to setup the TransferHandler when the label is created, rather then having to establish each one individually each time you want to re-use the solution.

What it's going to come down to is this. Do you want a quick, once of solution, or do you want something to is self-contained and re-usable. The Map and clientProperty choices are relatively quick to implement and use. The custom component will take a little more effort but will generally result in a more re-usable solution. It's all up to you ;)

Drag image from browser to drop onto JLabel

Your code seems to be just fine. I tested it with the following code, and drag-and-drop of an image from my webbrowser (Google Chrome) onto the JPanel sets the image in the JLabel.

import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.TransferHandler;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Image;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

public class DragAndDropTester {
private static final JLabel TEST_LABEL = new JLabel( "TestLabel" );

public static void main( String[] args ) {
try {
EventQueue.invokeAndWait( new Runnable() {
public void run() {

JFrame testFrame = new JFrame( "Test" );

JPanel contents = new JPanel( new BorderLayout() );
contents.add( TEST_LABEL, BorderLayout.CENTER );

contents.setTransferHandler( createTransferHandler() );

testFrame.getContentPane().add( contents );
testFrame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
testFrame.setSize( 200, 200 );
testFrame.setVisible( true );
}
} );
} catch ( InterruptedException e ) {
} catch ( InvocationTargetException e ) {
}
}

private static TransferHandler createTransferHandler(){
return new TransferHandler( ){
@Override
public boolean importData( JComponent comp, Transferable aTransferable ) {
try {
Object transferData = aTransferable.getTransferData( DataFlavor.imageFlavor );
TEST_LABEL.setIcon( new ImageIcon( ( Image ) transferData ) );
} catch ( UnsupportedFlavorException e ) {
} catch ( IOException e ) {
}
return true;
}

@Override
public boolean canImport( JComponent comp, DataFlavor[] transferFlavors ) {
return true;
}
};
}
}

This was tested on my Mac using JDK1.6

java version "1.6.0_29"
Java(TM) SE Runtime Environment (build 1.6.0_29-b11-402-10M3527)
Java HotSpot(TM) 64-Bit Server VM (build 20.4-b02-402, mixed mode)


Related Topics



Leave a reply



Submit