Reputation: 4328
I have two JTables, and they are set up so that you can drag and drop the rows within each JTable. The problem is that it lets me drag a row from one JTable to the other JTable, and I am trying to figure out how to stop that. I only want the user to be able to drag and drop a row within that same JTable.
In other words, when I drag a row outside the current table and hover the mouse over the empty panel space, the mouse cursor displays a circle with a diagonal line through it, which is what I want. However, when I drag the mouse over the other table, it displays the small rectangular "drop" icon, which is what I am trying to block. When the user tries to drag this row on top of other table, I would like the small circle with diagonal line to appear.
This is a working example that demonstrates the problem:
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DragSource;
import java.util.ArrayList;
import java.util.List;
import javax.activation.ActivationDataFlavor;
import javax.activation.DataHandler;
import javax.swing.DropMode;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.TransferHandler;
import javax.swing.table.AbstractTableModel;
public class JTableDnD extends JFrame
{
private JTable tableA;
private JTable tableB;
public JTableDnD()
{
// *** Create First Table ***
List<Object[]> dataA = new ArrayList<Object[]>();
dataA.add(new Object[] {"A1", "A1"});
dataA.add(new Object[] {"A2", "A2"});
dataA.add(new Object[] {"A3", "A3"});
List<String> columnsA = new ArrayList<String>();
columnsA.add("Column 1");
columnsA.add("Column 2");
tableA = new JTable(new TableModel(columnsA, dataA));
tableA.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
tableA.setDragEnabled(true);
tableA.setDropMode(DropMode.INSERT_ROWS);
tableA.setFillsViewportHeight(true);
tableA.setTransferHandler(new TableTransferHandler(tableA));
JScrollPane scrollPaneA = new JScrollPane(tableA);
// *** Create Second Table ***
List<Object[]> dataB = new ArrayList<Object[]>();
dataB.add(new Object[] {"B1", "B1"});
dataB.add(new Object[] {"B2", "B2"});
dataB.add(new Object[] {"B3", "B3"});
List<String> columnsB = new ArrayList<String>();
columnsB.add("Column 1");
columnsB.add("Column 2");
tableB = new JTable(new TableModel(columnsB, dataB));
tableB.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
tableB.setDragEnabled(true);
tableB.setDropMode(DropMode.INSERT_ROWS);
tableB.setFillsViewportHeight(true);
tableB.setTransferHandler(new TableTransferHandler(tableB));
JScrollPane scrollPaneB = new JScrollPane(tableB);
// *** Add ScrollPanes to Panel ***
this.getContentPane().setLayout(new FlowLayout());
add(scrollPaneA);
JPanel emptyPanel = new JPanel();
emptyPanel.setPreferredSize(new Dimension(100, 200));
add(emptyPanel);
add(scrollPaneB);
} // end JTableDnD constructor
private static void createAndShowGUI()
{
JFrame frame = new JTableDnD();
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String[] args)
{
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
interface Reorderable
{
public void reorder(int from, int to);
}
class TableModel extends AbstractTableModel implements Reorderable
{
private List<String> columnNames;
private List<Object[]> data;
public TableModel(List<String> columnNames, List<Object[]> data)
{
super();
this.columnNames = columnNames;
this.data = data;
}
@Override
public void reorder(int from, int to)
{
if (from < to)
{
to--;
}
Object[] row = data.remove(from);
data.add(to, row);
fireTableDataChanged();
}
@Override
public int getRowCount()
{
return data.size();
}
@Override
public int getColumnCount()
{
return columnNames.size();
}
@Override
public String getColumnName(int column)
{
return columnNames.get(column);
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
return data.get(rowIndex)[columnIndex];
}
} // end TableModel
class TableTransferHandler extends TransferHandler
{
private final DataFlavor localObjectFlavor = new ActivationDataFlavor(Integer.class, DataFlavor.javaJVMLocalObjectMimeType, "Integer Row Index");
private JTable table = null;
public TableTransferHandler(JTable table)
{
this.table = table;
}
@Override
protected Transferable createTransferable(JComponent component)
{
return new DataHandler(new Integer(table.getSelectedRow()), localObjectFlavor.getMimeType());
}
@Override
public boolean canImport(TransferHandler.TransferSupport support)
{
boolean b = support.getComponent() == table &&
support.isDrop() &&
support.isDataFlavorSupported(localObjectFlavor);
table.setCursor(b ? DragSource.DefaultMoveDrop : DragSource.DefaultMoveNoDrop);
return b;
}
@Override
public int getSourceActions(JComponent component)
{
return TransferHandler.COPY_OR_MOVE;
}
@Override
public boolean importData(TransferHandler.TransferSupport support)
{
JTable target = (JTable) support.getComponent();
JTable.DropLocation dropLocation = (JTable.DropLocation) support.getDropLocation();
int index = dropLocation.getRow();
int max = table.getModel().getRowCount();
if (index < 0 || index > max)
{
index = max;
}
target.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
try
{
Integer rowFrom = (Integer) support.getTransferable().getTransferData(localObjectFlavor);
if (rowFrom != -1 && rowFrom != index)
{
((Reorderable) table.getModel()).reorder(rowFrom, index);
if (index > rowFrom)
{
index--;
}
target.getSelectionModel().addSelectionInterval(index, index);
return true;
}
}
catch (Exception e)
{
e.printStackTrace();
}
return false;
}
@Override
protected void exportDone(JComponent component, Transferable transferable, int action)
{
if (action == TransferHandler.MOVE)
{
table.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
} // end TableTransferHandler
I believe I need to add some extra logic to the canImport() method to make sure that the row that is being dropped is from that same table, but I can't seem to figure it out. I've tried inspecting the data inside the TransferSupport object that gets passed into canImport(), but it does not seem to have any function that returns the exact JTable object source.
Upvotes: 1
Views: 1622
Reputation: 51525
In the simplified Swing DnD, there is no support for getting at the source of the transferable. The component returned by TransferSupport` is the target of the drop, that is the component that somehow should handle the associated transferable. So if you have a per-component TransferHandler configured with a particular table, the support's component will always be that same table instance and your check will be true trivially.
If you want to enable/disable dropping based on the sender, you'll have to provide it on a per-drag basis: a drag starts in exportAsDrag and ends in exportDone, so you can set/null the sender in those.
@Override
protected void exportDone(JComponent component,
Transferable transferable, int action) {
table = null;
}
@Override
public void exportAsDrag(JComponent comp, InputEvent e, int action) {
table = (JTable) comp;
super.exportAsDrag(comp, e, action);
}
Now you can re-use the same instance of the handler across several instances of table:
TableTransferHandler handler = new TableTransferHandler();
tableA.setTransferHandler(handler);
tableB.setTransferHandler(handler);
As an aside: I wouldn't fiddle with the cursors, they are supporsed to be under complete control of the dnd subsystem.
Upvotes: 1