使用 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);
}
}
当然可以在 updateItem
和 prepareContextMenu
中进行更复杂的检查,并且可以支持不同的用户交互(TextFieldTreeCell
可能不是合适的 superclass适合你;当用户在上下文菜单中选择 MenuItem
时,你可以使用“正常”TreeCell
并显示不同的 stage/dialog 来编辑项目。
关于细胞工厂的一些说明
细胞工厂用于在显示数据的 class 中创建细胞(例如 TableColumn
、TreeView
、ListView
)。当这样的 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)。
希望能帮到更多人。
基本上,我想知道我是否可以创建一棵树并在 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);
}
}
当然可以在 updateItem
和 prepareContextMenu
中进行更复杂的检查,并且可以支持不同的用户交互(TextFieldTreeCell
可能不是合适的 superclass适合你;当用户在上下文菜单中选择 MenuItem
时,你可以使用“正常”TreeCell
并显示不同的 stage/dialog 来编辑项目。
关于细胞工厂的一些说明
细胞工厂用于在显示数据的 class 中创建细胞(例如 TableColumn
、TreeView
、ListView
)。当这样的 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)。
希望能帮到更多人。