Thomas Thomas - 9 days ago 6
Java Question

Drop location rendering with swing

I want to implement custom drop location rendering for a component (e.g. JPanel or JLabel). My goal is to display a blue border when the user hovers the component with a droppable item and remove the border again when leaving the component.

The swing tutorial provides this:

Drop Location Rendering

Which didn't help me at all. I'm missing something like

enterComponent(Event e)
and
exitComponent(Event e)
, or does this page refer to something different than what I expected?

So I searched through the world wide web and found some examples (mostly from this blog). After a little while I could accomplish to put together a working example for what I needed (source below). But the examples I found all used classes from java.awt to render the drop location. Because I still haven't got the full overview over all these drag-and-drop classes, I didn't find a way to achieve the custom drop location rendering by only using swing classes.

So my question is:

Is is possible to render a JComponent on
"enterComponent"
and
"exitComponent"
events by only using swing classes?


Additional question:

If I remove the line
new DropTarget(label, dropListener);
,
ListItemTransfereHandler.importData(...)
will be called otherwise it won't be called. What is the reason for this? I would really appreciate some class and/or action chart about the dnd classes (this is not neccessary for an answer).

(No JavaFX answers please)




Source code of working example (with AWT classes). Sorry for the massive lines of code, but java dnd needs really much space.

public class DnDTransferableTest {

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new DnDTransferableTest();
}
});
}

public DnDTransferableTest() {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);

// JPanel panel = new JPanel(new MigLayout("wrap 2, fill", "fill, grow", "align center"));
JPanel panel = new JPanel(new GridLayout(1,2));

JList<ListItem> list = new JList<ListItem>();
JLabel label = new JLabel();

// Initializing list
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);

// Initializing label and its drop listener
label.setText("Drag on me...");
label.setTransferHandler(new ListTransferHandler());

DropTargetListener dropListener = new JLabelDropTargetListener();
new DropTarget(label, dropListener);

// Adding the components to the panel
panel.add(new JScrollPane(list), "sg test");
panel.add(label, "sg test");

frame.setContentPane(panel);
frame.setVisible(true);
}

}

class ListItem implements Transferable {

public static final DataFlavor LIST_ITEM_DATA_FLAVOR = new DataFlavor(ListItem.class, ListItem.class.getName());
private String text;
private int number;

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

public String getText() {
return text;
}

public int getNumber() {
return this.number;
}

@Override
public String toString() {
return this.getText() + ": " + this.getNumber();
}

@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 this;
}
}

class ListTransferHandler extends TransferHandler {

private static final long serialVersionUID = 1L;

@Override
public boolean canImport(TransferSupport support) {
return (support.getComponent() instanceof JLabel) && support.isDataFlavorSupported(ListItem.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(ListItem.LIST_ITEM_DATA_FLAVOR);
if (value instanceof ListItem) {
Component component = support.getComponent();
if (component instanceof JLabel) {
((JLabel) component).setText(((ListItem) value).getNumber() + ". " + ((ListItem) value).getText());
}
}
} catch (Exception exp) {
exp.printStackTrace();
}
}
System.out.println("import data " + accept);
return accept;
}

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

@Override
protected Transferable createTransferable(JComponent c) {

if (c instanceof JList<?>) {
JList<?> list = (JList<?>) c;
Object value = list.getSelectedValue();
if (value instanceof ListItem) {
return (ListItem) value;
}
}
return null;
}

@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...
}

}

class JLabelDropTargetListener implements DropTargetListener {

private int thickness = 2;
private Border blueBorder = BorderFactory.createLineBorder(Color.BLUE, thickness);
private Border emptyBorder = BorderFactory.createEmptyBorder(thickness, thickness, thickness, thickness);

@Override
public void dragEnter(DropTargetDragEvent dtde) {
System.out.println("dragEnter");
Object src = dtde.getDropTargetContext().getComponent();

if (src instanceof JLabel) {
JLabel label = (JLabel) src;
label.setForeground(Color.RED);
label.setBorder(blueBorder);
} else {
System.out.println(src.getClass().getName());
System.out.println(dtde.getDropTargetContext().getComponent());
}
}

@Override
public void dragOver(DropTargetDragEvent dtde) {
}

@Override
public void dropActionChanged(DropTargetDragEvent dtde) {
}

@Override
public void dragExit(DropTargetEvent dte) {
System.out.println("dragExit");
Component src = dte.getDropTargetContext().getComponent();

if (src instanceof JLabel) {
JLabel label = (JLabel) src;
label.setForeground(null);
label.setBorder(emptyBorder);
}

}

@Override
public void drop(DropTargetDropEvent dtde) {
System.out.println("drop");
Component src = dtde.getDropTargetContext().getComponent();

if (src instanceof JLabel) {
JLabel label = (JLabel) src;
label.setForeground(null);
label.setBorder(emptyBorder);

Transferable t = dtde.getTransferable();
Object value;
try {
value = t.getTransferData(ListItem.LIST_ITEM_DATA_FLAVOR);

if (value instanceof ListItem) {
ListItem li = (ListItem) value;
label.setText(li.getNumber() + ". " + li.getText());
}

} catch (UnsupportedFlavorException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

}
}

}

Answer

I looked deeper into the source code of the swing's TransferHandler and found JComponent's
setDropLocation(TransferHandler.DropLocation location, Object state, boolean forDrop)
which is called by TransferHandler on every DragEvent (TransferHandler has its own private DropTargetListener class). First I thought of overriding it in my own JLabel extending class, but then noticed it was protected.

Whit this new information available I stumbled over this two posts:

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6448332
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6500024

So the answer is: It is a bug or rather a not yet implemented feature (I don't know if its available in java 8, but I think JavaFX does it better than swing anyway. Unfortunately switching to JavaFX is a little to late now).