JavaFX:如何处理从TreeView拖动项目 [英] JavaFX: how to handle dragging an item from a TreeView

查看:119
本文介绍了JavaFX:如何处理从TreeView拖动项目的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

For an application I'm developing I have a TreeView with (my own type of) TreeItems. This is working fine, and I get the items to display as expected.

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

I now want to be able to handle dragging an item from this TreeView to another part of the app window and have it perform some action there. I am now faced with two (at least…) issues:


  1. 每当您在TreeView中单击时,该项始终处于选中状态。这可以防止这种情况吗?

  2. 在TreeView上添加MouseEvent监听器时,我得到了能够检测拖动并响应的事件。但我无法为鼠标事件确定相应的TreeItem。当然,我需要知道确切的TreeItem才能使拖动工作。这可能吗?

我尝试过的一些事情:


  1. 我添加了自己的单元工厂,甚至当处理和消耗单元格上的所有鼠标事件时,树中的项目仍然被选中???

  2. 如果我添加了MouseEvent处理程序到每个单元格,我将能够管理拖放,但鉴于TreeView中可能有数千个(可能是>> 100,000,而不是所有扩展的强硬)行,这不是一个巨大的开销和为TreeView只有一个事件处理程序会不会更好? (但是,如何确定相应的TreeItem?)

TreeView鼠标事件为我提供以下信息:

The TreeView mouse events give me the following info:

没有单击单元格: MouseEvent [source = TreeView [id = templateTreeView,styleClass = tree-view],target = TreeViewSkin $ 1 @ 32a37c7a [styleClass = cell indexed- cell tr​​ee-cell]'null',eventType = MOUSE_PRESSED,consume = false,x = 193.0,y = 289.0,z = 0.0,button = PRIMARY,primaryButtonDown,pickResult = PickResult [node = TreeViewSkin $ 1 @ 32a37c7a [styleClass = cell indexed -cell tr​​ee-cell]'null',point = Point3D [x = 192.0,y = 8.0,z = 0.0],距离= 1492.820323027551]

单击文本属性的单元格: MouseEvent [source = TreeView [id = templateTreeView,styleClass = tree-view],target = TreeViewSkin $ 1 @ 16aa9102 [styleClass = cell indexed-cell tr​​ee-cell ]'Attributes',eventType = MOUSE_PRESSED,consume = false,x = 76.0,y = 34.0,z = 0.0,button = PRIMARY,primaryButtonDown,pickResult = PickR esult [node = TreeViewSkin $ 1 @ 16aa9102 [styleClass = cell indexed-cell tr​​ee-cell]'Attributes',point = Point3D [x = 75.0,y = 13.0,z = 0.0],distance = 1492.820323027551]

Cell with text "Attributes" clicked: MouseEvent [source = TreeView[id=templateTreeView, styleClass=tree-view], target = TreeViewSkin$1@16aa9102[styleClass=cell indexed-cell tree-cell]'Attributes', eventType = MOUSE_PRESSED, consumed = false, x = 76.0, y = 34.0, z = 0.0, button = PRIMARY, primaryButtonDown, pickResult = PickResult [node = TreeViewSkin$1@16aa9102[styleClass=cell indexed-cell tree-cell]'Attributes', point = Point3D [x = 75.0, y = 13.0, z = 0.0], distance = 1492.820323027551]

我猜秘密是在PickResult的节点中的某个地方,但从那里我仍然无法看到如何到达TreeItem。

I guess the secret is somewhere in the Node of the PickResult, but from there I'm still unable to see how to get to the TreeItem.

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

Hope there is an (easy) answer to this...

推荐答案

你承诺过早优化的罪过:)。

You are committing the sin of premature optimization :).

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

TreeCells are essentially only created for the currently visible items in a TreeView. When you expand or collapse nodes in the tree, or when you scroll, those TreeCells are reused to display different TreeItems. This is the purpose of the updateItem(...) and similar methods in TreeCell; they are called when the item displayed by that TreeCell instance changes.

A TreeCell 在我的系统上大约1/4英寸高;显示100,000 TreeCell s将需要超过2,000英尺/ 630米高的显示器。此时,您可能比一些额外的侦听器有更严重的内存分配问题....但无论如何,只有在特定单元格上发生事件时才会调用侦听器,并且与之相比占用的占用空间相当小。单元本身,所以除非你有任何直接证据在单元格上注册监听器(正如你所观察到的那样,大大降低了你的代码复杂性)对性能产生负面影响,你应该使用每个单元的监听器方法。

A TreeCell on my system is about 1/4 inch high; to display 100,000 TreeCells would take a monitor more than 2,000 feet / 630 meters tall. At that point, you probably have more serious memory allocation issues than some extra listeners.... But at any rate, a listener would only be invoked if an event occurs on that particular cell, and occupies a fairly small footprint in comparison to the cell itself, so unless you have any direct evidence registering listeners on the cells (which as you've observed, massively reduces your code complexity) adversely affects performance, you should use the "listener per cell" approach.

以下是一个树的示例,它包含1,000,000 Integer - 值树项。它跟踪创建的 TreeCell 的数量(在我的系统上,我设置的窗口大小似乎永远不会超过20)。它还显示一个标签;您可以将值从树中拖动到标签,标签将显示已丢弃的值的运行总计。

Here is an example of a tree that holds 1,000,000 Integer-valued tree items. It tracks the number of TreeCells created (on my system it never seems to exceed 20 with the window size I set). It also displays a label; you can drag the values from the tree to the label and the label will display a running total of the values dropped there.

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);
    }
}

对于选择问题:我会问你为什么要这么做去做这个;它会创造一种不寻常的用户体验。问题可能是管理选择的烘焙事件处理程序在您定义的处理程序之前被调用,因此当您使用该事件时,选择已经被更改。您可以尝试添加事件过滤器:

For the selection issue: I would question why you want to do this; it would create an unusual user experience. The issue is probably that the "baked-in" event handlers which manage selection are being invoked before the handlers you define, so by the time you consume the event, selection has already been changed. You can try adding an event filter instead:

cell.addEventFilter(MouseEvent.MOUSE_PRESSED, Event::consume);

但这也会禁用扩展/折叠树中的节点。

but this will also disable expanding/collapsing the nodes in the tree.

所以你可以尝试类似的东西:

So you can try something like:

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();
});

此时它开始看起来像是黑客......

at which point it starts to look like something of a hack...

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

If you want to entirely disable selection, another option might be to create a custom selection model for the tree which just always returns an empty selection.

这篇关于JavaFX:如何处理从TreeView拖动项目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆