JavaFX:如何处理从树视图拖动项目

JavaFX:如何处理从树视图拖动项目,java,treeview,javafx-8,Java,Treeview,Javafx 8,对于我正在开发的应用程序,我有一个带有(我自己类型的)TreeItems的TreeView。这很好,我可以按预期显示项目 我现在希望能够处理将一个项目从这个树视图拖到应用程序窗口的另一部分,并让它在那里执行一些操作。我现在面临两个(至少…)问题: 每当您在树视图中单击时,项目始终处于选中状态。这能预防吗 在TreeView上添加MouseeEvent侦听器时,我将获得能够检测拖动并对此做出响应的事件。但是,我无法确定鼠标事件对应的TreeItem。当然,我需要知道确切的树名,才能让拖拽起作用。这

对于我正在开发的应用程序,我有一个带有(我自己类型的)TreeItems的TreeView。这很好,我可以按预期显示项目

我现在希望能够处理将一个项目从这个树视图拖到应用程序窗口的另一部分,并让它在那里执行一些操作。我现在面临两个(至少…)问题:

  • 每当您在树视图中单击时,项目始终处于选中状态。这能预防吗
  • 在TreeView上添加MouseeEvent侦听器时,我将获得能够检测拖动并对此做出响应的事件。但是,我无法确定鼠标事件对应的TreeItem。当然,我需要知道确切的树名,才能让拖拽起作用。这可能吗
  • 我尝试过的一些事情:

  • 我添加了我自己的单元格工厂,即使在处理和使用单元格上的所有鼠标事件时,树中的项目仍然处于选中状态
  • 如果我在每个单元格中添加一个MouseEvent处理程序,我将能够管理拖放操作,但是考虑到TreeView中可能有数千行(可能>>100000行,并非全部展开),这不是一个巨大的开销吗?对于TreeView来说,只有一个事件处理程序不是更好吗?(那么,如何确定对应的TreeItem呢?)
  • TreeView鼠标事件为我提供了以下信息:

    未单击单元格:
    MouseEvent[source=TreeView[id=templateTreeView,styleClass=tree-view],target=TreeViewSkin$1@32a37c7a[styleClass=单元索引单元树单元]'null',eventType=鼠标按下,消耗=false,x=193.0,y=289.0,z=0.0,button=PRIMARY,primaryButtonDown,pickResult=pickResult[node=TreeViewSkin$1@32a37c7a[styleClass=单元索引单元树单元]'null',point=Point3D[x=192.0,y=8.0,z=0.0],距离=1492.820323027551]

    单击文本“属性”的单元格:
    MouseEvent[source=TreeView[id=templateTreeView,styleClass=TreeView],target=TreeViewSkin$1@16aa9102[styleClass=单元索引单元树单元]“属性”,eventType=鼠标按下,消耗=假,x=76.0,y=34.0,z=0.0,button=PRIMARY,primaryButtonDown,pickResult=pickResult[node=TreeViewSkin$1@16aa9102[styleClass=单元索引单元树单元]“属性”,点=点3D[x=75.0,y=13.0,z=0.0],距离=1492.820323027551]

    我猜秘密就在PickResult节点的某个地方,但从那里我仍然看不到如何到达TreeItem


    希望有一个(简单的)答案…

    你犯了过早优化的罪:)

    TreeCell
    s基本上只为
    TreeView
    中当前可见的项目创建。当您展开或折叠树中的节点时,或者当您滚动时,这些
    TreeCell
    s被重用以显示不同的
    TreeItem
    s。这就是
    updateItem(…)
    TreeCell
    中的类似方法;当该
    TreeCell
    实例显示的项目发生更改时,将调用这些方法

    在我的系统上,一个
    TreeCell
    大约有1/4英寸高;要显示100000个
    TreeCell
    s需要一个超过2000英尺/630米高的监视器。在这一点上,你可能比一些额外的监听器有更严重的内存分配问题……但无论如何,只有在该部分发生事件时才会调用监听器因此,除非您有任何直接证据表明在单元格上注册侦听器(正如您所观察到的,这大大降低了代码复杂性)会对性能产生不利影响,否则您应该使用“每个单元格侦听器”方法

    下面是一个树的示例,其中包含1000000个
    整数
    值的树项目。它跟踪创建的
    树单元格
    的数量(在我的系统上,设置的窗口大小似乎从未超过20个)。它还显示一个标签;您可以将值从树拖到标签上,标签将显示拖放到其中的值的运行总数

    import java.util.stream.IntStream;
    
    import javafx.application.Application;
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.concurrent.Task;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.control.TreeCell;
    import javafx.scene.control.TreeItem;
    import javafx.scene.control.TreeView;
    import javafx.scene.input.ClipboardContent;
    import javafx.scene.input.DataFormat;
    import javafx.scene.input.Dragboard;
    import javafx.scene.input.TransferMode;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Stage;
    
    public class TreeViewNoSelection extends Application {
    
    
        private static int cellCount =  0 ;
        private final DataFormat objectDataFormat = new DataFormat("application/x-java-serialized-object");
    
        @Override
        public void start(Stage primaryStage) {
            TreeView<Integer> tree = new TreeView<>();
            tree.setShowRoot(false);
    
            Task<TreeItem<Integer>> buildTreeTask = new Task<TreeItem<Integer>>() {
    
                @Override
                protected TreeItem<Integer> call() throws Exception {
                    TreeItem<Integer> treeRoot = new TreeItem<>(0);
    
    
                    IntStream.range(1, 10).mapToObj(this::createItem)
                        .forEach(treeRoot.getChildren()::add);
                    return treeRoot ;
                }
                private TreeItem<Integer> createItem(int value) {
                    TreeItem<Integer> item = new TreeItem<>(value);
                    if (value < 100_000) {
                        for (int i = 0; i < 10; i++) {
                            item.getChildren().add(createItem(value * 10 + i));
                        }
                    }
                    return item ;
                }
    
            };
    
    
    
            tree.setCellFactory(tv -> new TreeCell<Integer>() {
    
                {               
                    System.out.println("Cells created: "+(++cellCount));
    
                    setOnDragDetected(e -> {
                        if (! isEmpty()) {
                            Dragboard db = startDragAndDrop(TransferMode.COPY);
                            ClipboardContent cc = new ClipboardContent();
                            cc.put(objectDataFormat, getItem());
                            db.setContent(cc);
                            Label label = new Label(String.format("Add %,d", getItem()));
                            new Scene(label);
                            db.setDragView(label.snapshot(null, null));
                        }
                    });
                }
    
                @Override
                public void updateItem(Integer value, boolean empty) {
                    super.updateItem(value, empty);
                    if (empty) {
                        setText(null);
                    } else {
                        setText(String.format("%,d", value));
                    }
                }
            });
    
            IntegerProperty total = new SimpleIntegerProperty();
            Label label = new Label();
            label.textProperty().bind(total.asString("Total: %,d"));
    
            label.setOnDragOver(e -> 
                    e.acceptTransferModes(TransferMode.COPY));
    
            // in real life use a CSS pseudoclass and external CSS file for the background:
            label.setOnDragEntered(e -> label.setStyle("-fx-background-color: yellow;"));
            label.setOnDragExited(e -> label.setStyle(""));
    
            label.setOnDragDropped(e -> {
                Dragboard db = e.getDragboard();
                if (db.hasContent(objectDataFormat)) {
                    Integer value = (Integer) db.getContent(objectDataFormat);
                    total.set(total.get() + value);
                    e.setDropCompleted(true);
                }
            });
    
            BorderPane.setMargin(label, new Insets(10));
            label.setMaxWidth(Double.MAX_VALUE);
            label.setAlignment(Pos.CENTER);
    
            BorderPane root = new BorderPane(new Label("Loading..."));
    
            buildTreeTask.setOnSucceeded(e -> {
                tree.setRoot(buildTreeTask.getValue());
                root.setCenter(tree);
                root.setBottom(label);
            });
    
            primaryStage.setScene(new Scene(root, 250, 400));
            primaryStage.show();
    
            Thread t = new Thread(buildTreeTask);
            t.setDaemon(true);
            t.start();
    
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    
    但这也将禁用展开/折叠树中的节点

    因此,您可以尝试以下方法:

    cell.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
        if (getTreeItem() != null) {
             Object target = e.getTarget();
             if (target instanceof Node && ((Node)target).getStyleClass().contains("arrow")) {
                 getTreeItem().setExpanded(! getTreeItem().isExpanded());
             }
         }
         e.consume();
    });
    
    在这一点上,它开始看起来像一个黑客的东西


    如果您想完全禁用选择,另一个选项可能是为树创建一个自定义选择模型,该模型总是返回一个空选择。

    考虑到这肯定很有趣,我稍后会仔细阅读(我可能需要它!)幸运的是,现在我正在拖到画布上,为此我已经有了确定目标的代码,我用选择模型创建了一个示例,但我不确定完全禁用选择是否是您想要的。如果有帮助,我可以发布。太棒了,谢谢您的详细回答!我很喜欢“犯了早熟优化的罪”!所以解决方案确实是只使用TreeCells。事实上,这样做让一切变得非常简单!我现在有了一个可用的拖放功能。它缺少了对用户的视觉确认,即他/她正在拖动某个东西,但我想这应该很容易实现(有任何建议吗?)至于不希望发生选择,这是因为我有一个选择模型更改侦听器,它将触发我在本例中不希望发生的更改。但是现在我可以检测从MouseEvent处理程序中选择的节点,我不需要这样做,我也可以使我的代码更干净!您应该看到一个默认的拖动图标w当拖动开始时。如果需要,您可以在
    dragDetected
    处理程序中使用
    dragboard.setDragView(..)
    对其进行配置。好的,对。我现在了解拖放事件并使其正常工作。但是,我现在遇到了以下问题:我需要知道确切的(本地)放置的坐标。DRAG_drop事件不包含这些,但是(?)。On
    cell.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
        if (getTreeItem() != null) {
             Object target = e.getTarget();
             if (target instanceof Node && ((Node)target).getStyleClass().contains("arrow")) {
                 getTreeItem().setExpanded(! getTreeItem().isExpanded());
             }
         }
         e.consume();
    });