【问题标题】:JavaFX TreeView with Drag&Drop functionality produces an exception具有拖放功能的 JavaFX TreeView 产生异常
【发布时间】:2018-04-20 21:29:06
【问题描述】:

我是 JavaFX 的新手。我想实现一个具有拖放功能的 JavaFX TreeView
我执行以下操作:我创建一个 TreeView 实例 (treeView) 和一个 ProgramRootTreeItem 类型的根节点(此类扩展 TreeItem)。
我向根节点添加了一些 ProgramTreeItem 类型的子节点(这也扩展了 TreeItem)。
在此之后,我将根节点添加到 treeView 并向其注册 cellFactory (DnDTreeCell)。
DnDTreeCell 实现有关拖放的所有事件处理。
我可以编译,但如果我执行,我会得到一个异常:

Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to pluginmanagement.ProgramTreeItem
at pluginmanagement.DnDTreeCell.updateItem(DnDTreeCell.java:15)

我完全不知道为什么会出现这个异常。 你能帮我吗? 提前谢谢。

DnDTreeApp.java

package pluginmanagement;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeItem.TreeModificationEvent;
import javafx.scene.control.TreeView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;


public class DnDTreeApp extends Application {

    public static void main(String[] args) {
        Application.launch(DnDTreeApp.class, args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Example Dynamic Tree");
        primaryStage.setResizable(true);
        final VBox box = new VBox();
        box.setFillWidth(false);
        Scene scene = new Scene(box);
        primaryStage.setScene(scene);
        box.getChildren().add(this.getExampleTree());
        primaryStage.show();
    }


    private TreeView<ProgramTreeItem> getExampleTree() {
        TreeView<ProgramTreeItem> treeView = new TreeView<ProgramTreeItem>();
        ProgramRootTreeItem root = buildTree();

        treeView.setCellFactory(new Callback<TreeView<ProgramTreeItem>, TreeCell<ProgramTreeItem>>() {
            @Override
            public TreeCell call(TreeView<ProgramTreeItem> param) {
                System.out.format("\ncall() - param: %s\n", param);
                TreeCell cell = new DnDTreeCell(param);
                System.out.format("\ncall() - return: %s\n", cell);
                return cell;
            }
        });

        treeView.setPrefSize(1000, 750);
        treeView.setShowRoot(true);
        treeView.setRoot(root);
        root.setExpanded(true);

        return treeView;
    }


    private ProgramRootTreeItem buildTree() {
        ProgramRootTreeItem root = new ProgramRootTreeItem();
        for(int a=1; a<4; a++) {
            for(int b=1; b<4; b++) {
                ProgramTreeItem child = new ProgramTreeItem("child_" + a + "_" + b);
                root.getChildren().add(child);
            }
        }
        return root;
    }

}

DnDTreeCell.java

package pluginmanagement;

import javafx.event.EventHandler;
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.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;


public class DnDTreeCell extends TreeCell<ProgramTreeItem> {
    private TreeView<ProgramTreeItem> parentTree;
    private ProgramTreeItem item;


    public DnDTreeCell(final TreeView<ProgramTreeItem> parentTree) {
        System.out.format("\nCTR - DnDTreeCell: [%s]\n", parentTree);
        this.parentTree = parentTree;

        // ON SOURCE NODE.
        setOnDragDetected(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                System.out.println("\nDrag detected on " + item);
                if (item == null) {
                    return;
                }
                Dragboard dragBoard = startDragAndDrop(TransferMode.MOVE);
                ClipboardContent content = new ClipboardContent();
                content.put(DataFormat.PLAIN_TEXT, item.toString());
                dragBoard.setContent(content);
                event.consume();
            }
        });
        setOnDragDone(new EventHandler<DragEvent>() {
            @Override
            public void handle(DragEvent dragEvent) {
                System.out.println("\nDrag done on " + item);
                dragEvent.consume();
            }
        });
        // ON TARGET NODE.
    //            setOnDragEntered(new EventHandler<DragEvent>() {
    //                @Override
    //                public void handle(DragEvent dragEvent) {
    //                    System.out.println("Drag entered on " + item);
    //                    dragEvent.consume();
    //                }
    //            });
        setOnDragOver(new EventHandler<DragEvent>() {
            @Override
            public void handle(DragEvent dragEvent) {
                System.out.println("\nDrag over on " + item);
                if (dragEvent.getDragboard().hasString()) {
                    String valueToMove = dragEvent.getDragboard().getString();
                    if (!valueToMove.matches(item.toString())) {
                        // We accept the transfer!!!!!
                        dragEvent.acceptTransferModes(TransferMode.MOVE);
                    }
                }
                dragEvent.consume();
            }
        });
    //            setOnDragExited(new EventHandler<DragEvent>() {
    //                @Override
    //                public void handle(DragEvent dragEvent) {
    //                    System.out.println("Drag exited on " + item);
    //                    dragEvent.consume();
    //                }
    //            });
        setOnDragDropped(new EventHandler<DragEvent>() {
            @Override
            public void handle(DragEvent dragEvent) {
                String valueToMove = dragEvent.getDragboard().getString();
                ProgramTreeItem itemToMove = search((ProgramTreeItem) parentTree.getRoot(), valueToMove);
                ProgramTreeItem newParent = search((ProgramTreeItem) parentTree.getRoot(), item.toString());
                if (!newParent.getParent().equals(itemToMove)) {
                    // Remove from former parent.
                    System.out.format("\nDrag dropped on " + item + ";  ItemToMove: %s;  NewParent: %s\n", itemToMove, newParent);
                    itemToMove.getParent().getChildren().remove(itemToMove);
                    // Add to new parent.
                    newParent.getChildren().add(itemToMove);
                    newParent.setExpanded(true);
                } else {
                    System.out.format("\nDrop not Allowed !\n");
                }
                dragEvent.consume();
            }
        });
    }

    private ProgramTreeItem search(final ProgramTreeItem currentNode, final String valueToSearch) {
        ProgramTreeItem result = null;
        if (currentNode.toString().matches(valueToSearch)) {
            result = currentNode;
        } else if (!currentNode.isLeaf()) {
            for (Object ch : currentNode.getChildren()) {
                result = search((ProgramTreeItem)ch, valueToSearch);
                if (result != null) {
                    break;
                }
            }
        }
        return result;
    }

    @Override
    protected void updateItem(ProgramTreeItem item, boolean empty) {
        System.out.format("\nupdateItem - [%s]\n", item);
        super.updateItem(item, empty);
        this.item = item;
        String text = (item == null) ? null : item.toString();
        setText(text);
    }
}

ProgramRootTreeItem.java

package pluginmanagement;


public class ProgramRootTreeItem extends ProgramTreeItem {

    public ProgramRootTreeItem() {
        super("ROOT");
    }


    @Override
    public boolean isRoot() {
        return true;
    }

    @Override
    public String toString() {
        return "ProgramRootTreeItem_"+getUserObject();
    }
}

ProgramTreeItem.java

package pluginmanagement;

import javafx.scene.control.TreeItem;


public class ProgramTreeItem extends TreeItem {
    String userObj;

    public ProgramTreeItem(String data) {
        super(data);
        userObj = data;
    }

    public String getUserObject() {
        return userObj;
    }

    public boolean isRoot() {
        return false;
    }

    @Override
    public String toString() {
        return "ProgramTreeItem_" + getUserObject();
    }
}

【问题讨论】:

  • 我不能完全遵循整个逻辑,但我很确定TreeCell 的类型参数应该是数据的类型,而不是TreeItem。在您的情况下,由于 ProgramTreeItem 是(隐式)TreeItem&lt;String&gt;DnDTreeCell 可能应该扩展 TreeCell&lt;String&gt; 而不是 TreeCell&lt;ProgramTreeItem&gt;

标签: javafx treeview


【解决方案1】:

您错误地使用了TreeView

你被允许打电话的唯一原因

treeView.setRoot(root);

是您自定义的TreeItem 实现扩展了原始类型而不是指定类型参数。否则编译器会抱怨方法调用。

TreeItems 只定义了树的结构。传递给单元格的实际值存储在TreeItemvalue 属性中。您所有的自定义TreeItems 都存储Strings。

TreeView 稍后尝试用来自TreeItems 的值填充它的单元格时,它会从TreeItems 获得Strings 但无法使用这些值作为第一个参数调用updateItem,因为DnDTreeCell 期望 ProgramTreeItem

实际上,您的自定义 TreeItem 类不包含您无法从传统 TreeItem 轻松获得的信息:

public static boolean isRoot(TreeItem<?> item) {
    return item.getParent() == null;
}

我建议使用TreeItem&lt;String&gt;s 重写代码:

private TreeView<String> getExampleTree() {
    TreeView<String> treeView = new TreeView<>();
    TreeItem<String> root = buildTree();

    treeView.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
        @Override
        public TreeCell call(TreeView<String> param) {
            System.out.format("\ncall() - param: %s\n", param);
            TreeCell<String> cell = new DnDTreeCell();
            System.out.format("\ncall() - return: %s\n", cell);
            return cell;
        }
    });

    treeView.setPrefSize(1000, 750);
    treeView.setShowRoot(true);
    treeView.setRoot(root);
    root.setExpanded(true);

    return treeView;
}

private TreeItem<String> buildTree() {
    TreeItem<String> root = new TreeItem<>();
    for(int a=1; a<4; a++) {
        for(int b=1; b<4; b++) {
            TreeItem<String> child = new TreeItem<>("child_" + a + "_" + b);
            root.getChildren().add(child);
        }
    }
    return root;
}
public class DnDTreeCell extends TreeCell<String> {

    public DnDTreeCell() {

        // ON SOURCE NODE.
        setOnDragDetected(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                TreeItem<String> item = getTreeItem();
                if (isEmpty() || item == null || item.getParent() == null) {
                    return; // don't start drag on empty cell or root cell
                }
                Dragboard dragBoard = startDragAndDrop(TransferMode.MOVE);
                ClipboardContent content = new ClipboardContent();
                content.put(DataFormat.PLAIN_TEXT, item.getValue());
                dragBoard.setContent(content);
                event.consume();
            }
        });
        setOnDragDone(new EventHandler<DragEvent>() {
            @Override
            public void handle(DragEvent dragEvent) {
                dragEvent.consume();
            }
        });
        setOnDragOver(new EventHandler<DragEvent>() {
            @Override
            public void handle(DragEvent dragEvent) {
                if (!(dragEvent.getGestureSource() instanceof TreeCell)
                        || ((TreeCell) dragEvent.getGestureSource()).getTreeView() != getTreeView()) {
                    return; // only allow dragging from this TreeView
                }
                TreeItem<String> sourceItem = ((TreeCell<String>) dragEvent.getGestureSource()).getTreeItem();

                // prevent item from being added to own subtree
                TreeItem<String> item = getTreeItem();
                while (item != null && item != sourceItem) {
                    item = item.getParent(); // go up through the tree until dragged item or root is found
                }
                if (item == null) {
                    // We accept the transfer!!!!!
                    dragEvent.acceptTransferModes(TransferMode.MOVE);
                }
                dragEvent.consume();
            }
        });
        setOnDragDropped(new EventHandler<DragEvent>() {
            @Override
            public void handle(DragEvent dragEvent) {
                TreeItem<String> itemToMove = ((TreeCell<String>) dragEvent.getGestureSource()).getTreeItem();
                TreeItem<String> newParent = getTreeItem();
                // Remove from former parent.
                itemToMove.getParent().getChildren().remove(itemToMove);
                // Add to new parent.
                newParent.getChildren().add(itemToMove);
                newParent.setExpanded(true);
                dragEvent.setDropCompleted(true);
                dragEvent.consume();
            }
        });
    }

    @Override
    protected void updateItem(String item, boolean empty) {
        System.out.format("\nupdateItem - [%s]\n", item);
        super.updateItem(item, empty);
        setText(item == null ? "" : item);
    }
}

【讨论】:

  • 我尝试在拖动时设置鼠标当前所在的 TreeCell 的背景颜色。它的唯一限制是,整个 TreeView 的背景从白色变为蓝色。我想问题是,在 setOnDragOver() 和 setOnDragDropped() 中,我无法正确获取 TreeView 的背景颜色。在@fabian 的帖子中,此代码用 //+++ 标记。你有想法让它变得正确吗?再次感谢。
  • @mondo80 如果您在实现附加功能时遇到错误,您应该提出一个新问题。如果您想提供上下文,可以添加指向新问题的链接。编辑此线程的帖子以添加其他问题不是要走的路。为此目的编辑答案更糟糕......(我因此拒绝了您的编辑。)
猜你喜欢
  • 1970-01-01
  • 2020-01-12
  • 1970-01-01
  • 1970-01-01
  • 2021-06-11
  • 2013-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多