我有一个 JTable,它支持通过拖放行进行重新排序。这工作得很好。但是,每次在给定行上启动拖动时,鼠标悬停在的第一个后续行都会被选中。
尽管听起来很奇怪,但如果我单击一行(启动拖动)并将鼠标向右或向左移动(而不是向上或向下),这不会发生。在这种情况下(当将鼠标向右或向左移动时)启动拖动的行保持选中状态。
此行为适用于 java (Metal) l&f 以及 linux (GTK+) l&f。
它应该很有趣,我提供了一个SSCCE。
package com.tracker.workspace.ui.views.test;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.io.IOException;
import java.io.Serializable;
import java.util.UUID;
import javax.swing.DropMode;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.TransferHandler;
import javax.swing.TransferHandler.TransferSupport;
import javax.swing.table.DefaultTableModel;
public class Main {
private final JTable table;
public Main() {
table = new JTable();
table.setModel(new DefaultTableModel() {
@Override
public int getRowCount() {
return 5;
}
@Override
public int getColumnCount() {
return 5;
}
@Override
public Object getValueAt(int r, int c) {
return r * c;
}
});
// Set up drag and drop on table.
DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(table, DnDConstants.ACTION_MOVE, new DefaultDragToReorderTableGestureListener(table));
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setDragEnabled(true);
table.setDropMode(DropMode.INSERT_ROWS);
table.setTransferHandler(new DefaultDragToReorderTableTransferHandler(table) {
public boolean moveRow(int source, int target) {
return true;
}
});
// Set up frame.
JFrame frame = new JFrame("MainFrame");
frame.setSize(new Dimension(640, 480));
frame.getContentPane().add(table, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
public class DefaultDragToReorderTableGestureListener implements DragGestureListener {
private final JTable table;
private final int[] allowedDragColumns;
public DefaultDragToReorderTableGestureListener(JTable table) {
this.table = table;
this.allowedDragColumns = new int[0];
}
public DefaultDragToReorderTableGestureListener(JTable table, int[] allowedDragColumns) {
this.table = table;
this.allowedDragColumns = allowedDragColumns;
}
public void dragGestureRecognized(DragGestureEvent e) {
boolean canDrag = false;
//We will only allow people to drag specific columns.
if (allowedDragColumns.length > 0) {
for (int i = 0; i < allowedDragColumns.length; i++) {
if (table.convertColumnIndexToModel(table.columnAtPoint(e.getDragOrigin())) == allowedDragColumns[i]) {
canDrag = true;
}
}
} else {
canDrag = true;
}
if (!canDrag) {
return;
}
// Get source row.
//int sourceRow = table.convertRowIndexToModel(table.getSelectedRow());
int sourceRow = table.rowAtPoint(e.getDragOrigin().getLocation());
sourceRow = table.convertRowIndexToModel(sourceRow);
//We will only permit the user to drag rows within the same table,
//and not to other components. Thus, we do not need to create any
//transfer object to hold any kind of data.
e.startDrag(null, new IntegerTransferObject(sourceRow));
}
}
public abstract class DefaultDragToReorderTableTransferHandler extends TransferHandler {
private final JTable table;
private final int[] allowedDropColumns;
public DefaultDragToReorderTableTransferHandler(JTable table) {
this.table = table;
this.allowedDropColumns = new int[0];
}
public DefaultDragToReorderTableTransferHandler(JTable table, int[] allowedDropColumns) {
this.table = table;
this.allowedDropColumns = allowedDropColumns;
}
@Override
public boolean canImport(TransferSupport support) {
//Do not allow the user to perform any action which is not a drop action.
if (!support.isDrop()) {
return false;
}
//Do not allow the user to perform any action which involves a data
//flavor other that an empty flavor.
if (!support.getTransferable().isDataFlavorSupported(new DataFlavor(Integer.class, "IntegerTransferObject"))) {
return false;
}
//We will only allow people to drag specific columns.
boolean canDrop = false;
if (allowedDropColumns.length > 0) {
for (int i = 0; i < allowedDropColumns.length; i++) {
if (table.convertColumnIndexToModel(table.columnAtPoint(support.getDropLocation().getDropPoint())) == allowedDropColumns[i]) {
canDrop = true;
}
}
} else {
canDrop = true;
}
if (!canDrop) {
return false;
}
//Do not allow the user to do any drop action involving data from other components.
if (support.getComponent() != table) {
return false;
}
// Do not allow drops just above source row or just below source row.
try {
// Get source row.
Integer sourceRow = (Integer) support.getTransferable().getTransferData(new DataFlavor(Integer.class, "IntegerTransferObject"));
// Get target row.
int targetRow = table.rowAtPoint(support.getDropLocation().getDropPoint());
targetRow = table.convertRowIndexToModel(targetRow);
// Validate that drop row is not adjacent to drag row.
if (targetRow == sourceRow || sourceRow + 1 == targetRow || sourceRow - 1 == targetRow) {
return false;
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
public boolean importData(TransferSupport support) {
//Validate the drop action.
if (!canImport(support)) {
return false;
}
try {
//Get source row.
Integer sourceRow = (Integer) support.getTransferable().getTransferData(new DataFlavor(Integer.class, "IntegerTransferObject"));
//Get target row.
int targetRow = table.rowAtPoint(support.getDropLocation().getDropPoint());
targetRow = table.convertRowIndexToModel(targetRow);
//Reorder rows based on the user's gestures.
return moveRow(sourceRow, targetRow);
} catch (Exception e) {
return false;
}
}
/**
* Returns the table which this handler operates on.
*
* @return the <code>JTable</code> which this handler operates on.
*/
protected JTable getTable() {
return table;
}
/**
* Moves the row at source position to the target position. This method
* returns <b>false</b> by default, and is expected to be overridden.
*
* @param source an <code>int</code> being the row which is to be moved.
* @param target an <code>int</code> being the row's target position.
* @return a <code>boolean</code> indicating wether the move was carried
* out successfully.
*/
public abstract boolean moveRow(int source, int target);
}
public class AnonymousSource implements TransferObjectSource {
public UUID getSourceId() {
return null;
}
}
public class IntegerTransferObject extends TransferObject {
private final int integer;
private final TransferObjectSource source;
public IntegerTransferObject(int integer) {
this.integer = integer;
this.source = new AnonymousSource();
}
public IntegerTransferObject(int integer, TransferObjectSource source) {
this.integer = integer;
this.source = source;
}
public int getInteger() {
return integer;
}
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[]{new DataFlavor(Integer.class, "IntegerTransferObject")};
}
public boolean isDataFlavorSupported(DataFlavor dataFlavor) {
DataFlavor[] dataFlavors = getTransferDataFlavors();
for (int i = 0; i < dataFlavors.length; i++) {
if (dataFlavors[i].equals(dataFlavor)) {
return true;
}
}
return false;
}
public Object getTransferData(DataFlavor dataFlavor) throws UnsupportedFlavorException, IOException {
if (isDataFlavorSupported(dataFlavor)) {
return integer;
} else {
throw new UnsupportedFlavorException(dataFlavor);
}
}
@Override
public TransferObjectSource getSource() {
return source;
}
}
public abstract class TransferObject implements Transferable, Serializable {
public abstract TransferObjectSource getSource();
}
public interface TransferObjectSource {
public UUID getSourceId();
}
public static void main(String[] args) {
Main m = new Main();
}
}