如何在 JavaFX 8 中为复杂的 TreeView<T> 定义简单模型?
How to define a simple model for a complex TreeView<T> in JavaFX 8?
TLDR: How to implement JavaFX 8 TreeItem in a TreeView with
different object types?
我有 XML 配置具有以下对象结构。
[Configuration]
--- has 1-n --->
[Group]
--- has 1-n --->
[Topic]
我想在 TreeView 中显示此结构,如下所示。
Groups
|
+-- Group A
| |-- Topic A
| |-- Topic B
|
+-- Group B
|-- Topic C
|-- Topic D
看完TreeItem documentation I just can't think of a solution because the objects in my structure are not of the same type and so I can't just define a TreeItem<T>
with TreeItem<File>
like in the example at TreeItem.
我想知道如何为上述结构定义一个设计良好且满足以下要求的模型。
- 根据class叶子的类型显示不同的上下文菜单
- 可以在树的任何级别插入和删除叶子
- 轻松访问叶对象,以便我可以对它们调用 getters/setters
- 模型改变时树自动更新
有没有解决方案或者有好的文章有现成的解决方案?
所以在@kleopatra(非常感谢)指出我使用类型 <Object>
之后,我按如下方式做了。
public class TreeController implements Initializable {
...
@FXML
private TreeView<Object> treeView;
@Override
public void initialize(URL location, ResourceBundle resources) {
TreeItem<Object> root = new TreeItem<>("Groups");
for (Object object : cfg.getGroupList()) {
root.getChildren().add(createNode(object));
}
treeView.setRoot(root);
treeView.setCellFactory(p -> new TreeCellFactory());
root.setExpanded(true);
}
private TreeItem<Object> createNode(Object o) {
return new TreeItem<Object>(o) {
private boolean isLeaf;
private boolean isFirstTimeChildren = true;
private boolean isFirstTimeLeaf = true;
@Override
public ObservableList<TreeItem<Object>> getChildren() {
if (isFirstTimeChildren) {
isFirstTimeChildren = false;
super.getChildren().setAll(buildChildren(this));
}
return super.getChildren();
}
@Override
public boolean isLeaf() {
if (isFirstTimeLeaf) {
isFirstTimeLeaf = false;
if (o instanceof Topic) {
isLeaf = true;
}
}
return isLeaf;
}
private ObservableList<TreeItem<Object>> buildChildren(TreeItem<Object> treeItem) {
if (treeItem.getValue() instanceof Group) {
ObservableList<TreeItem<Object>> children = FXCollections.observableArrayList();
for (Object object : ((Group) treeItem.getValue()).getTopicList()) {
children.add(createNode(object));
}
return children;
}
return FXCollections.emptyObservableList();
}
};
}
private final class TreeCellFactory extends TreeCell<Object> {
private ContextMenu topicMenu = new ContextMenu();
private ContextMenu groupMenu = new ContextMenu();
public TreeCellFactory() {
MenuItem topicMenuItem = new MenuItem("- Item 1 -");
topicMenu.getItems().add(topicMenuItem);
MenuItem groupMenuItem = new MenuItem("- Item 1 -");
groupMenu.getItems().add(groupMenuItem);
}
@Override
public void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
setText(item.toString());
}
if (item instanceof Topic) {
setContextMenu(topicMenu);
}
if (item instanceof Group) {
setContextMenu(groupMenu);
}
}
}
}
请注意,我将 Topic
和 Group
中的 toString()
覆盖为我希望出现在树叶上的文本。
TLDR: How to implement JavaFX 8 TreeItem in a TreeView with different object types?
我有 XML 配置具有以下对象结构。
[Configuration]
--- has 1-n --->
[Group]
--- has 1-n --->
[Topic]
我想在 TreeView 中显示此结构,如下所示。
Groups
|
+-- Group A
| |-- Topic A
| |-- Topic B
|
+-- Group B
|-- Topic C
|-- Topic D
看完TreeItem documentation I just can't think of a solution because the objects in my structure are not of the same type and so I can't just define a TreeItem<T>
with TreeItem<File>
like in the example at TreeItem.
我想知道如何为上述结构定义一个设计良好且满足以下要求的模型。
- 根据class叶子的类型显示不同的上下文菜单
- 可以在树的任何级别插入和删除叶子
- 轻松访问叶对象,以便我可以对它们调用 getters/setters
- 模型改变时树自动更新
有没有解决方案或者有好的文章有现成的解决方案?
所以在@kleopatra(非常感谢)指出我使用类型 <Object>
之后,我按如下方式做了。
public class TreeController implements Initializable {
...
@FXML
private TreeView<Object> treeView;
@Override
public void initialize(URL location, ResourceBundle resources) {
TreeItem<Object> root = new TreeItem<>("Groups");
for (Object object : cfg.getGroupList()) {
root.getChildren().add(createNode(object));
}
treeView.setRoot(root);
treeView.setCellFactory(p -> new TreeCellFactory());
root.setExpanded(true);
}
private TreeItem<Object> createNode(Object o) {
return new TreeItem<Object>(o) {
private boolean isLeaf;
private boolean isFirstTimeChildren = true;
private boolean isFirstTimeLeaf = true;
@Override
public ObservableList<TreeItem<Object>> getChildren() {
if (isFirstTimeChildren) {
isFirstTimeChildren = false;
super.getChildren().setAll(buildChildren(this));
}
return super.getChildren();
}
@Override
public boolean isLeaf() {
if (isFirstTimeLeaf) {
isFirstTimeLeaf = false;
if (o instanceof Topic) {
isLeaf = true;
}
}
return isLeaf;
}
private ObservableList<TreeItem<Object>> buildChildren(TreeItem<Object> treeItem) {
if (treeItem.getValue() instanceof Group) {
ObservableList<TreeItem<Object>> children = FXCollections.observableArrayList();
for (Object object : ((Group) treeItem.getValue()).getTopicList()) {
children.add(createNode(object));
}
return children;
}
return FXCollections.emptyObservableList();
}
};
}
private final class TreeCellFactory extends TreeCell<Object> {
private ContextMenu topicMenu = new ContextMenu();
private ContextMenu groupMenu = new ContextMenu();
public TreeCellFactory() {
MenuItem topicMenuItem = new MenuItem("- Item 1 -");
topicMenu.getItems().add(topicMenuItem);
MenuItem groupMenuItem = new MenuItem("- Item 1 -");
groupMenu.getItems().add(groupMenuItem);
}
@Override
public void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
setText(item.toString());
}
if (item instanceof Topic) {
setContextMenu(topicMenu);
}
if (item instanceof Group) {
setContextMenu(groupMenu);
}
}
}
}
请注意,我将 Topic
和 Group
中的 toString()
覆盖为我希望出现在树叶上的文本。