14 树视图
在本章中,您可以学习如何在JavaFX应用程序中构建树结构,向树视图添加项目,处理事件,并通过实现和应用单元格工厂来自定义树单元格。
javafx.scene.control
包的TreeView
类提供了层次结构的视图。在每个树中,层次结构中最高的对象称为“根”。根包含多个子项,子项也可以有子项。没有子项的项称为“叶子”。
图14-1显示了一个带有树视图的应用程序的屏幕截图。
创建树视图
在JavaFX应用程序中构建树结构时,通常需要实例化TreeView
类,定义多个TreeItem
对象,将其中一个树项设置为根项,将根项添加到树视图中,并将其他树项添加到根项中。
您可以使用TreeItem
类的相应构造函数或调用setGraphic
方法为每个树项附加一个图标。建议图标的大小为16x16,但实际上,任何Node
对象都可以设置为图标,并且它将是完全交互的。
示例14-1是一个简单的树视图的实现,包含一个根项和五个叶子项。
示例14-1 创建树视图
import javafx.application.Application; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.StackPane; import javafx.stage.Stage; public class TreeViewSample extends Application { private final Node rootIcon = new ImageView( new Image(getClass().getResourceAsStream("folder_16.png")) ); public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setTitle("树视图示例"); TreeItem<String> rootItem = new TreeItem<> ("收件箱", rootIcon); rootItem.setExpanded(true); for (int i = 1; i < 6; i++) { TreeItem<String> item = new TreeItem<> ("消息" + i); rootItem.getChildren().add(item); } TreeView<String> tree = new TreeView<> (rootItem); StackPane root = new StackPane(); root.getChildren().add(tree); primaryStage.setScene(new Scene(root, 300, 250)); primaryStage.show(); } }
在for
循环中创建的所有树项都通过调用getChildren
和add
方法添加到根项中。您还可以使用addAll
方法而不是add
方法一次性包含所有先前创建的树项。
您可以在创建新的TreeView
对象时,在TreeView
类的构造函数中指定树的根项,如示例14-1所示,或者可以通过调用TreeView
类的setRoot
方法来设置根项。
setExpanded
方法在根项上调用,定义了树视图项的初始外观。默认情况下,所有的TreeItem
实例都是折叠的,如果需要,必须手动展开。将true
值传递给setExpanded
方法,这样当应用程序启动时,根树项看起来是展开的,如图14-2所示。
示例14-1创建了一个简单的树视图,其中包含了String
项。然而,树结构可以包含不同类型的项。使用TreeItem
构造函数的以下通用表示法来定义由树项表示的应用程序特定数据:TreeItem<T> (T value)
。T
值可以指定任何对象,例如UI控件或自定义组件。
与TreeView
类不同,TreeItem
类不扩展Node
类。因此,您不能对树项应用任何视觉效果或添加菜单。使用单元格工厂机制来克服这个障碍,并为树项定义尽可能多的自定义行为,以满足您的应用程序需求。
实现单元格工厂
单元格工厂机制用于生成TreeView
中表示单个TreeItem
的TreeCell
实例。当应用程序处理大量动态变化或按需添加的数据时,使用单元格工厂特别有帮助。
考虑一个可视化给定公司的人力资源数据并允许用户修改员工详细信息和添加新员工的应用程序。
示例14-2创建了Employee
类,并根据部门将员工分组。
示例14-2 创建人力资源树视图的模型
TreeItem<String> rootNode; this.rootNode = new TreeItem<>("我的公司人力资源", rootIcon); rootNode.setExpanded(true); for (Employee employee : employees) { TreeItem<String> empLeaf = new TreeItem<>(employee.getName()); boolean found = false; for (TreeItem<String> depNode : rootNode.getChildren()) { if (depNode.getValue().contentEquals(employee.getDepartment())){ depNode.getChildren().add(empLeaf); found = true; break; } } if (!found) { TreeItem<String> depNode = new TreeItem<>( employee.getDepartment(), new ImageView(depIcon) ); rootNode.getChildren().add(depNode); depNode.getChildren().add(empLeaf); } } TreeView<String> treeView = new TreeView<>(rootNode);
在示例14-2中,每个Employee
对象都有两个属性:name
和department
。与员工对应的TreeItem
对象被称为树叶,而与部门对应的树项被称为具有子项的树项。要创建的新部门的名称是通过调用getDepartment
方法从Employee
对象中检索的。
当您编译和运行此应用程序时,它将创建如图14-3所示的窗口。
使用示例14-2,您可以预览树视图及其项,但无法更改现有项或添加任何新项。示例14-3展示了一个实现了单元格工厂的修改版本的应用程序。修改后的应用程序使您能够更改员工的名称。
示例14-3 实现单元格工厂
treeView.setEditable(true); treeView.setCellFactory((TreeView<String> p) -> new TextFieldTreeCellImpl()); private final class TextFieldTreeCellImpl extends TreeCell<String> { private TextField textField; public TextFieldTreeCellImpl() { } @Override public void startEdit() { super.startEdit(); if (textField == null) { createTextField(); } setText(null); setGraphic(textField); textField.selectAll(); } @Override public void cancelEdit() { super.cancelEdit(); setText((String) getItem()); setGraphic(getTreeItem().getGraphic()); } @Override public void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (empty) { setText(null); setGraphic(null); } else { if (isEditing()) { if (textField != null) { textField.setText(getString()); } setText(null); setGraphic(textField); } else { setText(getString()); setGraphic(getTreeItem().getGraphic()); } } } private void createTextField() { textField = new TextField(getString()); textField.setOnKeyReleased((KeyEvent t) -> { if (t.getCode() == KeyCode.ENTER) { commitEdit(textField.getText()); } else if (t.getCode() == KeyCode.ESCAPE) { cancelEdit(); } }); } private String getString() { return getItem() == null ? "" : getItem().toString(); } }
setCellFactory
方法在treeView
对象上调用,覆盖了TreeCell
的实现,并根据TextFieldTreeCellImpl
类中的指定重新定义了树项。
TextFieldTreeCellImpl
类为每个树项创建了一个TextField
对象,并提供了处理编辑事件的方法。
请注意,您必须显式调用setEditable(true)
方法来启用编辑所有树项。
在示例14-3中编译和运行应用程序。然后尝试点击树中的员工并更改他们的姓名。 图14-4捕捉了在IT支持部门编辑树项的时刻。
按需添加新的树节点
修改树视图示例应用程序,以便人力资源代表可以添加新员工。参考示例14-4中的粗体代码行。这些代码行为对应部门的树节点添加了一个上下文菜单。当选择“添加员工”菜单项时,新的树节点将作为叶子节点添加到当前部门。
使用isLeaf
方法来区分部门树节点和员工树节点。
示例14-4 添加新的树节点
private final ContextMenu addMenu = new ContextMenu(); MenuItem addMenuItem = new MenuItem("添加员工"); addMenu.getItems().add(addMenuItem); addMenuItem.setOnAction((ActionEvent t) -> { TreeItem newEmployee = new TreeItem<>("新员工"); getTreeItem().getChildren().add(newEmployee); }); if ( !getTreeItem().isLeaf()&&getTreeItem().getParent()!= null ){ setContextMenu(addMenu); }
编译并运行应用程序。然后在树结构中选择一个部门并右键单击它。上下文菜单将出现,如图14-5所示。
当您从上下文菜单中选择“添加员工”菜单项时,新记录将添加到当前部门。如图14-6所示,显示了一个新的树项添加到了“财务部门”。
由于树项启用了编辑功能,您可以将默认的“新员工”值更改为适当的名称。
使用树单元编辑器
您可以使用API中提供的以下树单元编辑器:CheckBoxTreeCell
、ChoiceBoxTreeCell
、ComboBoxTreeCell
、TextFieldTreeCell
。这些类扩展了TreeCell
实现,以在单元格内呈现特定的控件。
示例14-5演示了在构建复选框的分层结构的UI中使用CheckBoxTreeCell
类的用法。
示例14-5 使用CheckBoxTreeCell类
import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.control.cell.CheckBoxTreeCell; import javafx.scene.layout.StackPane; import javafx.stage.Stage; public class TreeViewSample extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setTitle("树视图示例"); CheckBoxTreeItem<String> rootItem = new CheckBoxTreeItem<>("查看源文件"); rootItem.setExpanded(true); final TreeView tree = new TreeView(rootItem); tree.setEditable(true); tree.setCellFactory(CheckBoxTreeCell.<String>forTreeView()); for (int i = 0; i < 8; i++) { final CheckBoxTreeItem<String> checkBoxTreeItem = new CheckBoxTreeItem<>("示例" + (i+1)); rootItem.getChildren().add(checkBoxTreeItem); } tree.setRoot(rootItem); tree.setShowRoot(true); StackPane root = new StackPane(); root.getChildren().add(tree); primaryStage.setScene(new Scene(root, 300, 250)); primaryStage.show(); } }
示例14-5通过使用CheckBoxTreeItem
类而不是TreeItem来构建树视图。CheckBoxTreeItem
类专门设计用于支持树结构中的选中、未选中和不确定状态。一个CheckBoxTreeItem
实例可以是独立的或依赖的。如果一个CheckBoxTreeItem
实例是独立的,对其选择状态的任何更改都不会影响其父级和子级CheckBoxTreeItem
实例。默认情况下,所有的ChechBoxTreeItem
实例都是依赖的。
编译并运行示例14-5,然后选择“查看源文件”项。您应该看到图14-7中显示的输出,其中所有子项都被选中。
要使CheckBoxTreeItem
实例独立,使用setIndependent
方法:rootItem.setIndependent(true);
。
当您运行TreeViewSample应用程序时,其行为应如图14-8所示发生变化。
TreeTableView
控件提供了在表格形式中呈现树结构的附加功能。有关这些控件的更多信息,请参见Tree Table View章节。
相关文档