简介
JTable是swing中的表格插件,官方提供的DnD (drag and drop)指的是将数据从不同组件之间进行拖拽,如将JTable中某行的数据拖到JTextView。为了实现一个JTable中不同行之间进行拖拽,需要自己实现。
实现原理
- 自己新建一个类继承自
BasicTableUI
,重写createMouseInputListener
和paint
方法。
public class DragDropRowTableUI extends BasicTableUI {
@Override
protected MouseInputListener createMouseInputListener() {
return new MouseInputHandler() {
public void mousePressed(MouseEvent e) {
// 记录鼠标起始位置
}
public void mouseDragged(MouseEvent e) {
// 计算鼠标移动的位置
// 如果超过了上一行的中间位置,则向上挪动一行
// 如果超过了下一行的中间位置,则向下挪动一行
}
};
}
@Override
public void paint(Graphics g, JComponent c) {
super.paint(g, c);
// 使用copyArea方法将选中的行移动到鼠标的位置(鼠标位置为行中间高度)
// 使用fillRect方法将空白区域填充背景色(比如将最后一行往下拖的时候,中间会空出一段区域)
}
- 创建JTable时,指定UI为自定义的UI类
table = new JBTable(tableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setUI(new DragDropRowTableUI(true));
实现代码
具体实现细节中,还需要考虑一些因素,例如需要退出单元格的编辑模式,否则会导致编辑状态的组件覆盖了目标行的数据。
@RequiredArgsConstructor
public class DragDropRowTableUI extends BasicTableUI {
private boolean draggingRow = false;
private int startDragPoint;
private int dyOffset;
private int oldRowIndex = -1;
private int newRowIndex = -1;
@Setter
private DragDropConfig dragDropConfig;
@Override
protected MouseInputListener createMouseInputListener() {
return new DragDropRowMouseInputHandler();
}
@Override
public void paint(Graphics g, JComponent c) {
super.paint(g, c);
if (draggingRow) {
g.setColor(table.getParent().getBackground());
Rectangle cellRect = table.getCellRect(table.getSelectedRow(), 0, false);
g.copyArea(cellRect.x, cellRect.y, table.getWidth(), table.getRowHeight(), cellRect.x, dyOffset);
if (dyOffset < 0) {
g.fillRect(cellRect.x, cellRect.y + (table.getRowHeight() + dyOffset), table.getWidth(),
dyOffset * -1);
} else {
g.fillRect(cellRect.x, cellRect.y, table.getWidth(), dyOffset);
}
}
}
class DragDropRowMouseInputHandler extends MouseInputHandler {
public void mousePressed(MouseEvent e) {
// 鼠标按下获得起点位置
super.mousePressed(e);
startDragPoint = (int) e.getPoint().getY();
oldRowIndex = table.getSelectedRow();
}
public void mouseDragged(MouseEvent e) {
// 拖拽选中的行,稍微难点就在鼠标拖拽这一事件里面
int fromRow = table.getSelectedRow();
if (fromRow >= 0) {
draggingRow = true;
}
int rowHeight = table.getRowHeight();
// 获取选中行中间的Y坐标
int middleOfSelectedRow = (rowHeight * fromRow) + (rowHeight / 2);
int toRow = -1;
int yMousePoint = (int) e.getPoint().getY();
if (yMousePoint < (middleOfSelectedRow - rowHeight)) {
// Move row up
toRow = fromRow - 1;
} else if (yMousePoint > (middleOfSelectedRow + rowHeight)) {
// Move row down
toRow = fromRow + 1;
}
// 数据调换
boolean canDrag = true;
if (dragDropConfig != null && toRow >= 0 && toRow < table.getRowCount()) {
canDrag = dragDropConfig.canDrap(table.getModel(), fromRow, toRow);
}
if (canDrag) {
if (toRow >= 0 && toRow < table.getRowCount()) {
cancelEditing();
TableModel model = table.getModel();
for (int i = 0; i < model.getColumnCount(); i++) {
Object fromValue = model.getValueAt(fromRow, i);
Object toValue = model.getValueAt(toRow, i);
model.setValueAt(toValue, fromRow, i);
model.setValueAt(fromValue, toRow, i);
}
if (model instanceof RowNumberTableModel) {
((RowNumberTableModel)model).swapRowData(fromRow, toRow);
}
table.setRowSelectionInterval(toRow, toRow);
newRowIndex = toRow;
startDragPoint = yMousePoint;
}
} else {
startDragPoint = yMousePoint;
}
dyOffset = (startDragPoint - yMousePoint) * -1;
table.repaint();
}
public void mouseReleased(MouseEvent e) {
super.mouseReleased(e);
draggingRow = false;
if (newRowIndex >= 0 && newRowIndex < table.getRowCount() && newRowIndex != oldRowIndex) {
table.setRowSelectionInterval(newRowIndex, newRowIndex);
}
// 对表格的重新刷新。。。
table.repaint();
newRowIndex = -1;
oldRowIndex = -1;
}
private void cancelEditing() {
int row = table.getEditingRow();
int column = table.getEditingColumn();
if (row != -1 && column != -1) {
TableCellEditor cellEditor = table.getCellEditor(row, column);
if (cellEditor != null) {
cellEditor.cancelCellEditing();
}
}
}
}
@FunctionalInterface
public interface DragDropConfig {
boolean canDrap(TableModel model, int fromRow, int toRow);
}
}
本示例代码增加了一些特性,如:
- 增加某些行不能拖拽的配置功能。
- 如果TableModel为RowNumberTableModel(自定义类,实现了行序号、行额外数据功能),则在发生数据调换的时候,通知TableModel。