From 3941040362de46778a1641a5a4c5d81cbd0138d8 Mon Sep 17 00:00:00 2001 From: Timi Date: Fri, 24 Apr 2026 10:40:08 +0800 Subject: [PATCH] add drag sort --- .../imyeyu/hosts/ui/components/HostTable.java | 128 +++++++++++++++++- 1 file changed, 127 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/imyeyu/hosts/ui/components/HostTable.java b/src/main/java/com/imyeyu/hosts/ui/components/HostTable.java index 8f3f850..eb560d9 100644 --- a/src/main/java/com/imyeyu/hosts/ui/components/HostTable.java +++ b/src/main/java/com/imyeyu/hosts/ui/components/HostTable.java @@ -10,16 +10,22 @@ import com.imyeyu.lang.multi.ResourcesMultilingual; import javafx.beans.binding.DoubleBinding; import javafx.beans.property.BooleanProperty; import javafx.beans.property.StringProperty; +import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; +import javafx.scene.control.TableRow; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.Dragboard; +import javafx.scene.input.MouseEvent; import javafx.scene.input.ScrollEvent; +import javafx.scene.input.TransferMode; import lombok.RequiredArgsConstructor; /** @@ -31,7 +37,10 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class HostTable extends TableView implements TimiFXUI { + private static final String DRAG_INDEX_KEY = "host-table-row-index"; + private final ResourcesMultilingual multilingual; + private int dragSourceIndex = -1; @PostConstruct private void postConstruct() { @@ -78,6 +87,10 @@ public class HostTable extends TableView implements TimiFXUI { } }); getColumns().add(activated); + + TableColumn drag = createDragColumn(); + getColumns().add(drag); + // IP TableColumn ip = new TableColumn<>("IP"); ip.setCellValueFactory(new PropertyValueFactory<>("ip")); @@ -245,6 +258,7 @@ public class HostTable extends TableView implements TimiFXUI { DoubleBinding db = widthProperty().subtract(8); db = db.subtract(activated.widthProperty()); + db = db.subtract(drag.widthProperty()); db = db.subtract(ip.widthProperty()); db = db.subtract(description.widthProperty()); db = db.subtract(delete.widthProperty()); @@ -256,5 +270,117 @@ public class HostTable extends TableView implements TimiFXUI { // 滚动时编辑区主动失去焦点 addEventFilter(ScrollEvent.ANY, e -> requestFocus()); + setRowFactory(table -> createDraggableRow()); } -} \ No newline at end of file + + /** + * 创建拖动排序列。 + * + * @return 拖动排序列 + */ + private TableColumn createDragColumn() { + TableColumn drag = new TableColumn<>(""); + drag.setPrefWidth(40); + drag.setSortable(false); + drag.setResizable(false); + drag.setReorderable(false); + drag.setCellFactory(cell -> new TableCell<>() { + + private final Button dragButton = new Button("::"); + + { + dragButton.getStyleClass().add(CSS.BORDER_N); + dragButton.setFocusTraversable(false); + dragButton.setOnDragDetected(this::startRowDrag); + setAlignment(Pos.CENTER); + } + + @Override + protected void updateItem(Void item, boolean empty) { + super.updateItem(item, empty); + if (empty || getTableRow() == null || getTableRow().getItem() == null) { + setGraphic(null); + } else { + setGraphic(dragButton); + } + } + + /** + * 从拖动按钮发起行拖拽。 + * + * @param event 鼠标事件 + */ + private void startRowDrag(MouseEvent event) { + TableRow row = getTableRow(); + if (row == null || row.isEmpty()) { + return; + } + dragSourceIndex = row.getIndex(); + Dragboard dragboard = row.startDragAndDrop(TransferMode.MOVE); + ClipboardContent content = new ClipboardContent(); + content.putString(DRAG_INDEX_KEY.formatted()); + dragboard.setContent(content); + event.consume(); + } + }); + return drag; + } + + /** + * 创建支持拖动排序的表格行。 + * + * @return 表格行 + */ + private TableRow createDraggableRow() { + TableRow row = new TableRow<>(); + row.setOnDragOver(event -> { + if (dragSourceIndex < 0 || row.getIndex() == dragSourceIndex) { + return; + } + if (event.getDragboard().hasString()) { + event.acceptTransferModes(TransferMode.MOVE); + event.consume(); + } + }); + row.setOnDragDropped(event -> { + if (dragSourceIndex < 0) { + return; + } + int targetIndex = row.isEmpty() ? getItems().size() : row.getIndex(); + moveItem(dragSourceIndex, targetIndex); + event.setDropCompleted(true); + event.consume(); + }); + row.setOnDragDone(event -> dragSourceIndex = -1); + return row; + } + + /** + * 调整条目顺序。 + * + * @param fromIndex 原索引 + * @param toIndex 目标索引 + */ + private void moveItem(int fromIndex, int toIndex) { + ObservableList items = getItems(); + if (fromIndex < 0 || items.size() <= fromIndex) { + return; + } + if (toIndex < 0) { + toIndex = 0; + } + if (items.size() < toIndex) { + toIndex = items.size(); + } + if (fromIndex == toIndex) { + return; + } + Host movedItem = items.remove(fromIndex); + if (fromIndex < toIndex) { + toIndex--; + } + items.add(toIndex, movedItem); + getSelectionModel().clearAndSelect(toIndex); + scrollTo(toIndex); + } +}