OK, this problem is out of my league. I'm trying to implement a GUI widget in swing that allows files to be dropped onto a JTable, and allows the rows of the JTable to be dragged for re-sorting. Think VLC's playlists or the one in iTunes.
I got dropping files from the OS (Explorer, Finder, etc.) working just fine, but I'm having an impossible time with re-arranging the rows of the table, once the files are in. The problem is that when I add a custom TransferHandler to the table, dragging from the table is instantly killed. Here is some example code:
import javax.swing.*;
public class TableTest
{
public static void main (String [] argv)
{
// setup table data
String [] columns = new String [] {"Foo", "Bar", "Baz", "Quux"};
String [][] data = new String [][] {{"A", "B", "C", "D"},
{"1", "2", "3", "4"},
{"i", "ii", "iii", "iv"}};
// create table
JTable table = new JTable(data, columns);
// set up drag and drop
table.setDragEnabled(true);
table.setDropMode(DropMode.INSERT_ROWS);
table.setFillsViewportHeight(true);
TransferHandler dnd = new TransferHandler() {
// here be code to handle drops, and one would
// presume drag exporting, too
};
table.setTransferHandler(dnd);
JScrollPane scroll = new JScrollPane(table);
// create and show window
JFrame window = new JFrame();
window.getContentPane().add(scroll);
window.pack();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
Run this code as-is and you'll see that you cannot initiate a drag on the table.If you comment out the call to setTransferHandler() on the table, dragging works (i.e., when I start dragging a table row, I get the X'd out circle cursor saying I can't drop there). But as soon as a TransferHandler is set for the table, I can't drag any rows. The problem has to be in the TransferHandler, but I've thoroughly troubleshot and debugged it, and have determined that dragging is never started once there is a TransferHandler on the table. What am I doing wrong?
I had the same problem, it's got nothing to do with your custom implementation of the TransferHandler. When you replace the TransferHandler you also need to get a hold of the default DragSource and tell it to recognize drag gesture. You may also need to implement your own Transferable because you'll need to pass it to the DragGestureEvent.startDrag() method.
table.setTransferHandler(new MyTransferHandler());
table.setDragEnabled(true);
DragSource source = DragSource.getDefaultDragSource();
source.createDefaultDragGestureRecognizer(table, DnDConstants.ACTION_COPY, new DragGestureListener() {
@Override
public void dragGestureRecognized(DragGestureEvent dge) {
//grab the selected files from the table model
ArrayList<File> files = new ArrayList<File>();
for (int row : table.getSelectedRows()) {
files.add((File) dm.getValueAt(row, 1));
}
//FileTransferable is a custom Transferable implementation
Transferable transferable = new FileTransferable(files);
//and this is the magic right here
dge.startDrag(null,transferable);
}
});
It doesn't look like you are using the TransferHandler properly. Try to read through the tutorial here.
See the TransferHandler doc here. The empty constructor doesn't look like it's meant for usage outside a subclass of TransferHandler.
And you don't implement any of the functionality provided in the standard TransferHandler provided on Swing components. See exerpt from the DnD tutorial here (my bold):
Note: If you install a custom TransferHandler onto a Swing component, the default support is replaced. For example, if you replace JTextField's TransferHandler with one that handles colors only, you will disable its ability to support import and export of text. If you must replace a default TransferHandler — for example, one that handles text — you will need to re-implement the text import and export ability. This does not need to be as extensive as what Swing provides — it could be as simple as supporting the StringFlavor data flavor, depending on your application's needs.
I think the problem is that the empty TransferHandler actually prevents DnD events from occurring. There is a sample here which may be relevant.
http://www.java2s.com/Code/Java/Swing-JFC/ExtendedDnDDragandDropDemo.htm
I did not want to get into the nuts and bolts of what was going on so I just delegated the methods I wasn't interested to the old TransferHandler.
tree.setDragEnabled(true);
tree.setDropMode(DropMode.XXXX);
tree.setTransferHandler(new MyTransferHandler(tree.getTransferHandler());
Start with a standard setup but pass the old TransferHandler into your custom TransferHandler.
private class MyTransferHandler extends TransferHandler {
private TransferHandler delegate;
public MyTransferHandler(TransferHandler delegate) {
this.delegate = delegate;
}
public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
return delegate.canImport(comp, transferFlavors);
}
public boolean canImport(TransferSupport support) {
return true;
}
protected Transferable createTransferable(JComponent c) {
try {
Method method = delegate.getClass().getDeclaredMethod("createTransferable", JComponent.class);
method.setAccessible(true);
return (Transferable) method.invoke(delegate, c);
} catch (Exception e) {
return super.createTransferable(c);
}
}
public void exportAsDrag(JComponent comp, InputEvent event, int action) {
delegate.exportAsDrag(comp, event, action);
}
protected void exportDone(JComponent source, Transferable data, int action) {
try {
Method method = delegate.getClass().getDeclaredMethod("exportDone", JComponent.class, Transferable.class,
int.class);
method.setAccessible(true);
method.invoke(delegate, source, data, action);
} catch (Exception e) {
super.exportDone(source, data, action);
}
}
public int getSourceActions(JComponent c) {
return delegate.getSourceActions(c);
}
public Icon getVisualRepresentation(Transferable t) {
return delegate.getVisualRepresentation(t);
}
public boolean importData(JComponent comp, Transferable t) {
return delegate.importData(comp, t);
}
public boolean importData(TransferHandler.TransferSupport support) {
return delegate.importData(support);
}
}
One gotcha is that createTransferable(JComponent) and exportDone(JComponent, Transferable, int) methods are protected so you need to do reflection in order to delegate to those methods. When I didn't do this reflection delegation the strategy did not work. Once I did this delegation drag and drop worked as expected without changing the DragSource or having to write a new Transferable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With