mondo80
mondo80

Reputation: 11

JavaFX TreeView with Drag&Drop functionality produces an exception

I'm new to JavaFX. I would like to implement a JavaFX TreeView with Drag&Drop functionality.
I do the following: I create a TreeView instance (treeView) and a root node of type ProgramRootTreeItem (this class extends TreeItem).
To the root node I add some child node of type ProgramTreeItem (this also extends TreeItem).
After this I add the root node to treeView and register a cellFactory (DnDTreeCell) to it.
DnDTreeCell implements all the event handling regarding to Drag&Drop.
I can compile, but if I execute I get an exception:

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

I have absolutely no idea, why I get this exception. Can you help me please? Thanx in advance.

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

Upvotes: 1

Views: 391

Answers (1)

fabian
fabian

Reputation: 82461

You're using TreeView incorrectly.

The only reason you're allowed to call

treeView.setRoot(root);

is that your custom TreeItem implementations extend the raw type instead of specifying a type parameter. Otherwise the compiler would complain about the method call.

TreeItems only define the structure of the tree. The actual values that are passed to the cells are stored in the value property of the TreeItem. All your custom TreeItems store Strings.

When TreeView later attempts to fill it's cells with the values from the TreeItems it gets Strings from the TreeItems but is unable to call updateItem with these values as first parameter, since DnDTreeCell expects ProgramTreeItem.

Actually your custom TreeItem classes don't contain information that you couldn't get easily from a conventional TreeItem:

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

I recommend rewriting the code using TreeItem<String>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);
    }
}

Upvotes: 1

Related Questions