使用javafx创建自定义树

时间:2016-01-27 18:56:41

标签: java javafx treeview

基本上,我想知道我是否可以创建一个树并在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;
        }
    }

}

我正在质疑自己,这是否是正确的“技术”,它将解决我正在尝试做的事情......

我的目标是什么?

首先,我想添加或删除treeItem。我必须说某个treeItem只能添加一次或任意N次,比如限制(例如:treeItem&lt; 6表示特定级别的范围和树视图根的某个路径)。

其次,让一些treeItem可编辑,其他不可编辑!如果它是可编辑的,您可以弹出一些东西供用户插入一些输入,例如!

有可能吗?

我从https://docs.oracle.com/javafx/2/ui_controls/tree-view.htm#BABJGGGF看到了这个教程,但是我真的对这个教程感到困惑......我真的不了解单元工厂的机制......当我只想要它时,他确实适用于TreeView某个TreeItem ......或者我如何控制这种效果/行为? 我的意思是,我真的迷失了TreeView。可能,TreeView不是我想要的......

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

2 个答案:

答案 0 :(得分:4)

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

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

    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可能不适合您;您可以使用“普通”TreeCell并在用户在上下文菜单中选择MenuItem时显示不同的阶段/对话框来编辑项目。

关于细胞工厂的一些澄清

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

实施例

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

创建TreeView以显示包含2个未扩展子节点的扩展根节点。

TreeView确定它需要为根节点显示3个项目,并且它是2个子节点。 TreeView因此使用它的单元工厂来创建3个单元格并将它们添加到它的布局中并分配显示的项目。

现在,用户扩展了第一个孩子,它有2个孩子。 TreeView确定需要2个单元格来显示项目。为了提高效率,在布局结束时添加了新单元格,并更新了单元格的项目:

  • 以前包含最后一个子项的单元格已更新,现在包含第一个项目的第一个子项。
  • 更新了2个新添加的单元格,以包含第一个子节点的第二个子节点和根节点的第二个子节点。

答案 1 :(得分:0)

所以我决定取消TreeView(因为文档很垃圾......),而我决定实现Webview!

为什么?

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

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

此外,使用jstree创建/编辑树的可能性更好。 我得出结论,这是我能为我找到的最佳解决方案。

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

希望它会帮助更多人。