使用 javafx 创建自定义树

Creating a custom tree with javafx

基本上,我想知道我是否可以创建一棵树并在 javaFX 上对其进行自定义... 我尝试这样做,但到目前为止无法用这段代码做任何事情...

public class Main{
    ......

 public Main() throws Exception{
    ......    

   // TreeView created
    TreeView tv = (TreeView) fxmlLoader.getNamespace().get("treeview");

    TreeItem<String> rootItem = new TreeItem<String>("liss");
    rootItem.setExpanded(true);
    tv.setRoot(rootItem);

    /*for (int i = 1; i < 6; i++) {
        TreeItem<String> item = new TreeItem<String> ("Message" + i);
        rootItem.getChildren().add(item);
    }
    TreeItem<String> item = new TreeItem<String> ("MessageWoot");
    rootItem.getChildren().add(item);
*/
    //tv.setEditable(true);

    tv.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
        @Override
        public TreeCell<String> call(TreeView<String> arg0) {
            // custom tree cell that defines a context menu for the root tree item
            return new MyTreeCell();
        }
    });

    stage.show();
}

//
private static class MyTreeCell extends TextFieldTreeCell<String> {
    private ContextMenu addMenu = new ContextMenu();
    public boolean clickedFirstTime = false;

    public MyTreeCell() {
        // instantiate the root context menu
        MenuItem addMenuItem = new MenuItem("Expand");
        addMenu.getItems().add(addMenuItem);
        addMenuItem.setOnAction(new EventHandler() {

            public void handle(Event t) {
                TreeItem n0 =
                        new TreeItem<String>("'program'");
                TreeItem n1 =
                        new TreeItem<String>("<identifier>");
                TreeItem n2 =
                        new TreeItem<String>("body");

                getTreeItem().getChildren().add(n0);
                getTreeItem().getChildren().add(n1);
                getTreeItem().getChildren().add(n2);

            }
        });
    }

    @Override
    public void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);

        // if the item is not empty and is a root...
        //if (!empty && getTreeItem().getParent() == null && this.clickedFirstTime) {
        System.out.println("UPDATEITEM -> clickedFirstTime : "+this.clickedFirstTime);
        if (!this.clickedFirstTime) {
            System.out.println("WOOT");
            setContextMenu(addMenu);
            this.clickedFirstTime = true;
        }
    }

}

我在问自己这是否正确 "technology" 这将解决我正在尝试做的事情...

我的 objective 是什么?

首先,我要添加或删除一个 treeItem。我必须说某个 treeItem 可以只添加一次或任意 N 次,就像一个限制(例如:treeItem < 6 对于某个级别范围和树视图根的某个路径)。

其次,让一些treeItem可编辑,其他的不可编辑!当它是可编辑的时,您可以为用户弹出一些内容,以便插入一些输入,例如!

可能吗?

我看到了 https://docs.oracle.com/javafx/2/ui_controls/tree-view.htm#BABJGGGF 的教程,但我对这个教程真的很困惑......我不太了解细胞工厂机制......他确实适用于 TreeView 当我只想要某个 TreeItem... 或者我如何控制 effect/behaviour ? 我的意思是,我真的迷失了 TreeView。可能,TreeView 不是我要找的...

P.S.: 我知道我不能对树项目应用任何视觉效果或添加菜单,我使用细胞工厂机制来克服这个障碍。只是我不明白这个想法,我该怎么做!

如果您想使用 JavaFX,这当然是正确的“技术”。但是,您可能应该为 TreeItem 使用更复杂的类型参数。您可以使用自定义 TreeCell 来允许所需的用户交互。

此示例允许通过上下文菜单添加 children 和删除节点(除非内容是 "nocontext")以及编辑内容(只要内容不是 "noedit");在根节点上,删除 选项被禁用:

    tv.setEditable(true);
    
    tv.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {

        private final MyContextMenu contextMenu = new MyContextMenu();
        private final StringConverter converter = new DefaultStringConverter();
        
        @Override
        public TreeCell<String> call(TreeView<String> param) {
            return new CustomTreeCell(contextMenu, converter);
        }
    
    });
public class CustomTreeCell extends TextFieldTreeCell<String> {

    private final MyContextMenu contextMenu;

    public CustomTreeCell(MyContextMenu contextMenu, StringConverter<String> converter) {
        super(converter);
        if (contextMenu == null) {
            throw new NullPointerException();
        }
        this.contextMenu = contextMenu;
        this.setOnContextMenuRequested(evt -> {
            prepareContextMenu(getTreeItem());
            evt.consume();
        });
    }

    private void prepareContextMenu(TreeItem<String> item) {
        MenuItem delete = contextMenu.getDelete();
        boolean root = item.getParent() == null;
        if (!root) {
            delete.setOnAction(evt -> {
                item.getParent().getChildren().remove(item);
                contextMenu.freeActionListeners();
            });
        }
        delete.setDisable(root);
        contextMenu.getAdd().setOnAction(evt -> {
            item.getChildren().add(new TreeItem<>("new item"));
            contextMenu.freeActionListeners();
        });
    }

    @Override
    public void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        if (!empty) {
            setContextMenu("nocontext".equals(item) ? null : contextMenu.getContextMenu());
            setEditable(!"noedit".equals(item));
        }
    }

}
public class MyContextMenu {
    private final ContextMenu contextMenu;
    private final MenuItem add;
    private final MenuItem delete;

    public MyContextMenu() {
        this.add = new MenuItem("add child");
        this.delete = new MenuItem("delete");
        this.contextMenu = new ContextMenu(add, delete);
    }

    public ContextMenu getContextMenu() {
        return contextMenu;
    }

    public MenuItem getAdd() {
        return add;
    }

    public MenuItem getDelete() {
        return delete;
    }

    /**
     * This method prevents memory leak by setting all actionListeners to null.
     */
    public void freeActionListeners() {
        this.add.setOnAction(null);
        this.delete.setOnAction(null);
    }
    
}

当然可以在 updateItemprepareContextMenu 中进行更复杂的检查,并且可以支持不同的用户交互(TextFieldTreeCell 可能不是合适的 superclass适合你;当用户在上下文菜单中选择 MenuItem 时,你可以使用“正常”TreeCell 并显示不同的 stage/dialog 来编辑项目。

关于细胞工厂的一些说明

细胞工厂用于在显示数据的 class 中创建细胞(例如 TableColumnTreeViewListView)。当这样的 class 需要显示内容时,它会使用它的单元格工厂来创建它用来显示数据的 Cell。此类单元格中显示的内容可能会更改(请参阅 updateItem 方法)。

例子

(我不是 100% 确定这就是它完成的方式,但应该足够接近)

创建了一个TreeView来显示一个展开的根节点和2个未展开的children.

TreeView判断根节点需要显示3项,为2children。 TreeView 因此使用它的单元格工厂创建 3 个单元格并将它们添加到它的布局并分配显示的项目。

现在用户扩展了第一个 child,它有 2 个 children。 TreeView 确定它需要另外 2 个单元格来显示项目。新单元格添加在布局的末尾以提高效率并更新单元格的项目:

  • 之前包含最后一个 child 的单元格已更新,现在包含第一个项目的第一个 child。
  • 新添加的 2 个单元格更新为分别包含第一个 child 的第二个 child 和根的第二个 child。

所以我决定取消 TreeView(因为文档太垃圾了...),相反,我决定实现 Webview!

为什么?

像那样,我可以创建一个 HTML 文档并在其中使用 jstree(jquery 插件 - https://www.jstree.com)。这是一个基本上可以创建树视图的插件。

不幸的是,文档比 treeview oracle 文档好十倍。

另外,在creating/editing树中用jstree的可能性更好。 这让我得出结论,这是我能找到的最好的解决方案。

此外,无论谁会读我,我都在 webview 和我的 javafx 应用程序之间建立了桥梁!它是我的 HTML 文档和 java 应用程序之间的连接(在此处阅读更多信息 - https://blogs.oracle.com/javafx/entry/communicating_between_javascript_and_javafx)。

希望能帮到更多人。