如何通过鼠标拖动在JavaFX ListView中选择多行

Fay*_*aye 7 listview javafx

我是JavaFX的新手,我似乎找不到如何做到这一点。

我在Vbox中有一个ListView,其中装有一个ObservableList of Strings。我已将ListView的SelectionMode设置为MULTIPLE,这使我可以在按住Ctrl或Shift键的同时选择多个项目。

我希望能够单击一行并将鼠标向下拖动并选择多行,但是我不知道该怎么做。我尝试了几次搜索,似乎只找到拖放,而这不是我所需要的。

@FXML private ListView availableColumnList;

private ObservableList<String> availableColumns = FXCollections.<String>observableArrayList("One","Two","Three","Four");

availableColumnList.getItems().addAll(availableColumns);

availableColumnList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
Run Code Online (Sandbox Code Playgroud)

Sla*_*law 2

如果您使用的是 JavaFX 10+,那么您可以ListViewSkin在那里扩展和添加功能。您需要 JavaFX 10 或更高版本的原因是,那时扩展的VirtualContainerBase类添加了方法。然后,您可以使用动画 API(例如 )通过该方法滚动。ListViewSkingetVirtualFlow()AnimationTimerListViewVirtualFlow#scrollPixels(double)

下面是一个概念验证。它所做的只是ListView当鼠标靠近顶部(或左侧)或靠近底部(或右侧)时自动滚动ListView。当鼠标进入一个单元格时,该项目被选中(粗略地)。如果您想在开始向相反方向拖动鼠标时取消选择项目,那么您需要自己实现。您可能想要实现的另一件事是,AnimationTimer如果ListView隐藏或从场景中删除,则停止。

注意:下面使用“全按-拖动-释放”手势。换句话说,存在着MouseEvent处理程序和MouseDragEvent处理程序的混合体。使用 s 的原因MouseDragEvent是因为它们可以传递到其他节点,而不仅仅是原始节点(与“简单的按下-拖动-释放”手势不同)。查看此文档以获取更多信息。

主程序.java

import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.stage.Stage;

public final class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        var listView = IntStream.range(0, 1000)
                .mapToObj(Integer::toString)
                .collect(Collectors.collectingAndThen(
                        Collectors.toCollection(FXCollections::observableArrayList),
                        ListView::new
                ));
        // Sets the custom skin. Can also be set via CSS.
        listView.setSkin(new CustomListViewSkin<>(listView));
        listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        primaryStage.setScene(new Scene(listView, 600, 400));
        primaryStage.show();
    }

}
Run Code Online (Sandbox Code Playgroud)

自定义ListViewSkin.java

import javafx.animation.AnimationTimer;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.skin.ListViewSkin;
import javafx.scene.input.MouseDragEvent;
import javafx.scene.input.MouseEvent;

public class CustomListViewSkin<T> extends ListViewSkin<T> {

    private static final double DISTANCE = 10;
    private static final double PERCENTAGE = 0.05;

    private AnimationTimer scrollAnimation = new AnimationTimer() {

        @Override
        public void handle(long now) {
            if (direction == -1) {
                getVirtualFlow().scrollPixels(-DISTANCE);
            } else if (direction == 1) {
                getVirtualFlow().scrollPixels(DISTANCE);
            }
        }

    };

    private Rectangle2D leftUpArea;
    private Rectangle2D rightDownArea;

    private int direction = 0;
    private int anchorIndex = -1;

    public CustomListViewSkin(final ListView<T> control) {
        super(control);
        final var flow = getVirtualFlow();
        final var factory = flow.getCellFactory();

        // decorate the actual cell factory
        flow.setCellFactory(vf -> {
            final var cell = factory.call(flow);

            // handle drag start
            cell.addEventHandler(MouseEvent.DRAG_DETECTED, event -> {
                if (control.getSelectionModel().getSelectionMode() == SelectionMode.MULTIPLE) {
                    event.consume();
                    cell.startFullDrag();
                    anchorIndex = cell.getIndex();
                }
            });

            // handle selecting items when the mouse-drag enters the cell
            cell.addEventHandler(MouseDragEvent.MOUSE_DRAG_ENTERED, event -> {
                event.consume();
                if (event.getGestureSource() != cell) {
                    final var model = control.getSelectionModel();
                    if (anchorIndex < cell.getIndex()) {
                        model.selectRange(anchorIndex, cell.getIndex() + 1);
                    } else {
                        model.selectRange(cell.getIndex(), anchorIndex + 1);
                    }
                }
            });

            return cell;
        });

        // handle the auto-scroll functionality
        flow.addEventHandler(MouseDragEvent.MOUSE_DRAG_OVER, event -> {
            event.consume();
            if (leftUpArea.contains(event.getX(), event.getY())) {
                direction = -1;
                scrollAnimation.start();
            } else if (rightDownArea.contains(event.getX(), event.getY())) {
                direction = 1;
                scrollAnimation.start();
            } else {
                direction = 0;
                scrollAnimation.stop();
            }
        });

        // stop the animation when the mouse exits the flow/list (desired?)
        flow.addEventHandler(MouseDragEvent.MOUSE_DRAG_EXITED, event -> {
            event.consume();
            scrollAnimation.stop();
        });

        // handle stopping the animation and reset the state when the mouse
        // is released. Added to VirtualFlow because it doesn't matter
        // which cell receives the event.
        flow.addEventHandler(MouseEvent.MOUSE_RELEASED, event -> {
            if (anchorIndex != -1) {
                event.consume();
                anchorIndex = -1;
                scrollAnimation.stop();
            }
        });

        updateAutoScrollAreas();
        registerChangeListener(control.orientationProperty(), obs -> updateAutoScrollAreas());
        registerChangeListener(flow.widthProperty(), obs -> updateAutoScrollAreas());
        registerChangeListener(flow.heightProperty(), obs -> updateAutoScrollAreas());
    }

    // computes the regions where the mouse needs to be
    // in order to start auto-scrolling. The regions depend
    // on the orientation of the ListView.
    private void updateAutoScrollAreas() {
        final var flow = getVirtualFlow();
        switch (getSkinnable().getOrientation()) {
            case HORIZONTAL:
                final double width = flow.getWidth() * PERCENTAGE;
                leftUpArea = new Rectangle2D(0, 0, width, flow.getHeight());
                rightDownArea = new Rectangle2D(flow.getWidth() - width, 0, width, flow.getHeight());
                break;
            case VERTICAL:
                final double height = flow.getHeight() * PERCENTAGE;
                leftUpArea = new Rectangle2D(0, 0, flow.getWidth(), height);
                rightDownArea = new Rectangle2D(0, flow.getHeight() - height, flow.getWidth(), height);
                break;
            default:
                throw new AssertionError();
        }
    }

    @Override
    public void dispose() {
        unregisterChangeListeners(getSkinnable().orientationProperty());
        unregisterChangeListeners(getVirtualFlow().widthProperty());
        unregisterChangeListeners(getVirtualFlow().heightProperty());
        super.dispose();

        scrollAnimation.stop();
        scrollAnimation = null;
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:正如kleopatra 提到的,至少其中一些功能更适合行为类。然而,为了简单起见,我决定仅使用现有的公共皮肤类(通过扩展它)。再次强调,以上只是概念验证。