当前位置: 首页 > 知识库问答 >
问题:

JavaFX:如何处理从TreeView拖动项

钱俊楚
2023-03-14

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

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

    null
    null

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

未单击单元格:MouseEvent[源=TreeView[ID=TemplateTreeView,StyleClass=Tree-View],target=TreeViewSkin$1@32A37C7A[StyleClass=cell indexed-cell tree-cell]“null”,eventType=MOUSE_PRESSED,consumed=false,x=193.0,y=289.0,z=0.0,button=PRIMARY,primaryButtonDown,pickResult=pickResult[节点=TreeViewSkin$1@32A37C7A[StyleClass=cell indexed-cell tree-cell]“null”,point=Point3D[x=192.0,y=8.0,z=0.0],distance

单击了文本“Attributes”的单元格: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/代码>

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

希望对此有一个(容易的)答案...


共有1个答案

汝昀
2023-03-14

您犯了过早优化的罪:)。

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

我的系统上的treecell大约有1/4英寸高;要显示100,000个treecellS,需要一个2000英尺/630米高的监视器。此时,您可能会遇到比一些额外的侦听器更严重的内存分配问题....但无论如何,只有在特定单元格上发生事件时才会调用侦听器,并且与单元格本身相比占用的空间相当小,因此除非您有任何直接证据表明在单元格上注册侦听器(正如您所观察到的,这极大地降低了代码复杂性)会对性能产生不利影响,否则您应该使用“每个单元格都有侦听器”方法。

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, Event::consume);

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

因此您可以尝试以下操作:

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

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

 类似资料:
  • 问题内容: 对于我正在开发的应用程序,我有一个带有(我自己的类型)TreeItems的TreeView。一切正常,我可以按预期显示项目。 现在,我希望能够处理将一个项目从此TreeView拖到应用程序窗口的另一部分,并使其在此执行某些操作。我现在面临两个(至少……)问题: 每当您在TreeView中单击时,该项目始终处于选中状态。可以预防吗? 在TreeView上添加MouseEvent侦听器时,

  • 有人能举一个简单的例子来说明如何设置EventHandler以在窗格(JavaFX)上拖动图像视图。对于拖动,我的意思是在图像上按下鼠标,拖动和图像应该跟随,然后释放鼠标,图像视图将停止在该位置。

  • 我目前正在使用JavaFX ScrollBar控件,它本身运行良好-但是我对拖动拇指时的“动画”不满意。 更具体地说: 当我快速拖动拇指时,快速加速-拇指动画不会立即跟随,但有点“滞后”-当我停止拖动拇指时的相同行为。。。拇指真正开始/追上预定位置需要一秒钟的时间 这不是繁重的布局计算之类的问题,因为我只是单独渲染ScrollBar而没有任何内容。 是否有一些选项可以让这个“加速动画”/“滞后”消

  • 我试图在JavaFX中执行以下操作。我正在用画布在屏幕上画东西,我希望发生以下事情: 当我点击画布表面时(比如快速按下并释放):有些事情发生了 当我在画布表面拖动时(如按住并移动,然后释放):会发生其他事情 但是如果我拖动,我希望它排除单击操作,所以如果我拖动,如果我单击,会发生什么事情,而不应该发生。不幸的是,当我释放鼠标时,释放和单击事件都会启动,即使我拖动鼠标。

  • 我很惊讶,节点的原始大小显然是在中控制的。我们有两个愚蠢的方法和仅此而已。 事件在哪里,可观察的属性在哪里,所有这些进步的东西在哪里?被遗忘了?欢迎视窗3.1应用编程接口? 我的印象是不是错了?请修理我。 如何绑定节点边界?如何同步调整一个节点的大小和另一个节点的大小?如何将一个节点放入另一个节点?如何使内部节点推送外部节点的大小? 如何将一些动作与节点调整大小联系起来?

  • 我不希望GUI代码(即JavaFX类)出现在我的域对象中。 我需要编写一个适配器类来将我的域对象转换为JavaFX树吗?然后将侦听器添加到树中,并将更改映射回域对象?还是有更好的办法?