首页 > 解决方案 > 在 TableView 中拖动/重新排序列时水平滚动

问题描述

在 JavaFX 的TableView(and TreeTableView) 中,当水平滚动条存在时,使用拖放对列进行重新排序非常困难,因为当人们想要将列拖到当前不可见的位置时(离开滚动窗格视口),表格不会自动滚动.

我注意到已经有一个错误(增强)报告:

...但是由于它已经有一段时间没有得到解决了,我想知道是否有任何其他方法可以使用当前的 API 实现相同的行为。

有SSCCE:

public class TableViewColumnReorderDragSSCCE extends Application {

    public static final int NUMBER_OF_COLUMNS = 30;
    public static final int MAX_WINDOW_WIDTH = 480;

    @Override
    public void start(Stage stage) {
        stage.setScene(new Scene(createTable()));
        stage.show();
        stage.setMaxWidth(MAX_WINDOW_WIDTH);
    }

    private TableView<List<String>> createTable() {
        final TableView<List<String>> tableView = new TableView<>();
        initColumns(tableView);
        return tableView;
    }

    private void initColumns(TableView<List<String>> tableView) {
        for (int i=0; i<NUMBER_OF_COLUMNS; i++) {
            tableView.getColumns().add(new TableColumn<>("Column " + i));
        }
        tableView.getItems().add(Collections.emptyList());
    }
}

重现步骤:

  1. 运行上面的SSCCE
  2. 尝试拖动Column 0Column 29

在此处输入图像描述

我正在寻求一个功能齐全的解决方案(如果有的话)。

标签: javauser-interfacejavafx

解决方案


由于没有提供完整的解决方案,我想出了自己的一个。我介绍了一个 ( ColumnsOrderingEnhancer) 实现,它将通过自动滚动(在需要时)增强表视图列的重新排序。

用法(使用上述 SSCCE 中定义的表视图):

// Enhance table view columns reordering
final ColumnsOrderingEnhancer<List<String>> columnsOrderingEnhancer = new ColumnsOrderingEnhancer<>(tableView);
columnsOrderingEnhancer.init();

ColumnsOrderingEnhancer执行:

public class ColumnsOrderingEnhancer<T> {

    private final TableView<T> tableView;

    public ColumnsOrderingEnhancer(TableView<T> tableView) {
        this.tableView = tableView;
    }

    public void init() {
        tableView.skinProperty().addListener((observable, oldSkin, newSkin) -> {

            // This can be done only when skin is ready
            final TableHeaderRow header = (TableHeaderRow) tableView.lookup("TableHeaderRow");
            final MouseDraggingDirectionHelper mouseDraggingDirectionHelper = new MouseDraggingDirectionHelper(header);
            final ScrollBar horizontalScrollBar = getTableViewHorizontalScrollbar();

            // This is the most important part which is responsible for scrolling table during the column dragging out of the viewport.
            header.addEventFilter(MouseEvent.MOUSE_DRAGGED, event -> {
                final double totalHeaderWidth = header.getWidth();
                final double xMousePosition = event.getX();
                final MouseDraggingDirectionHelper.Direction direction = mouseDraggingDirectionHelper.getLastDirection();
                maybeChangeScrollBarPosition(horizontalScrollBar, totalHeaderWidth, xMousePosition, direction);
            });

        });
    }

    private void maybeChangeScrollBarPosition(ScrollBar horizontalScrollBar, double totalHeaderWidth, double xMousePosition, MouseDraggingDirectionHelper.Direction direction) {
        if (xMousePosition > totalHeaderWidth && direction == RIGHT) {
            horizontalScrollBar.increment();
        }
        else if (xMousePosition < 0 && direction == LEFT) {
            horizontalScrollBar.decrement();
        }
    }

    private ScrollBar getTableViewHorizontalScrollbar() {
        Set<Node> scrollBars = tableView.lookupAll(".scroll-bar");
        final Optional<Node> horizontalScrollBar =
                  scrollBars.stream().filter(node -> ((ScrollBar) node).getOrientation().equals(Orientation.HORIZONTAL)).findAny();
        try {
            return (ScrollBar) horizontalScrollBar.get();
        }
        catch (NoSuchElementException e) {
            return null;
        }
    }

    /**
     * A simple class responsible for determining horizontal direction of the mouse during dragging phase.
     */
    static class MouseDraggingDirectionHelper {

        private double xLastMousePosition = -1;
        private Direction direction = null;

        MouseDraggingDirectionHelper(Node node) {
            // Event filters that are determining when scrollbar needs to be incremented/decremented
            node.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> xLastMousePosition = event.getX());
            node.addEventFilter(MouseEvent.MOUSE_DRAGGED, event -> {
                direction = ((event.getX() - xLastMousePosition > 0) ? RIGHT : LEFT);
                xLastMousePosition = event.getX();
            });
        }

        enum Direction {
            LEFT,
            RIGHT
        }

        public Direction getLastDirection() {
            return direction;
        }
    }
    
}

最终结果(效果出奇的好):

在此处输入图像描述


推荐阅读