文档



JavaFX:使用JavaFX UI组件

14 树视图

在本章中,您可以学习如何在JavaFX应用程序中构建树结构,向树视图添加项目,处理事件,并通过实现和应用单元格工厂来自定义树单元格。

javafx.scene.control包的TreeView类提供了层次结构的视图。在每个树中,层次结构中最高的对象称为“根”。根包含多个子项,子项也可以有子项。没有子项的项称为“叶子”。

图14-1显示了一个带有树视图的应用程序的屏幕截图。

图14-1 树视图示例

图14-1的描述
"图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循环中创建的所有树项都通过调用getChildrenadd方法添加到根项中。您还可以使用addAll方法而不是add方法一次性包含所有先前创建的树项。

您可以在创建新的TreeView对象时,在TreeView类的构造函数中指定树的根项,如示例14-1所示,或者可以通过调用TreeView类的setRoot方法来设置根项。

setExpanded方法在根项上调用,定义了树视图项的初始外观。默认情况下,所有的TreeItem实例都是折叠的,如果需要,必须手动展开。将true值传递给setExpanded方法,这样当应用程序启动时,根树项看起来是展开的,如图14-2所示。

图14-2 带有五个树项的树视图

图14-2的描述
"图14-2 带有五个树项的树视图"的描述

示例14-1创建了一个简单的树视图,其中包含了String项。然而,树结构可以包含不同类型的项。使用TreeItem构造函数的以下通用表示法来定义由树项表示的应用程序特定数据:TreeItem<T> (T value)T值可以指定任何对象,例如UI控件或自定义组件。

TreeView类不同,TreeItem类不扩展Node类。因此,您不能对树项应用任何视觉效果或添加菜单。使用单元格工厂机制来克服这个障碍,并为树项定义尽可能多的自定义行为,以满足您的应用程序需求。

实现单元格工厂

单元格工厂机制用于生成TreeView中表示单个TreeItemTreeCell实例。当应用程序处理大量动态变化或按需添加的数据时,使用单元格工厂特别有帮助。

考虑一个可视化给定公司的人力资源数据并允许用户修改员工详细信息和添加新员工的应用程序。

示例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对象都有两个属性:namedepartment。与员工对应的TreeItem对象被称为树叶,而与部门对应的树项被称为具有子项的树项。要创建的新部门的名称是通过调用getDepartment方法从Employee对象中检索的。

当您编译和运行此应用程序时,它将创建如图14-3所示的窗口。

图14-3 树视图示例应用程序中的员工列表

图14-3的描述如下
"图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 更改员工姓名

图14-4的描述如下
"图14-4 更改员工姓名"的描述

按需添加新的树节点

修改树视图示例应用程序,以便人力资源代表可以添加新员工。参考示例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-5 添加新员工的上下文菜单

图14-5的描述如下
"图14-5 添加新员工的上下文菜单"的描述

当您从上下文菜单中选择“添加员工”菜单项时,新记录将添加到当前部门。如图14-6所示,显示了一个新的树项添加到了“财务部门”。

图14-6 新添加的员工

图14-6的描述如下
"图14-6 新添加的员工"的描述

由于树项启用了编辑功能,您可以将默认的“新员工”值更改为适当的名称。

使用树单元编辑器

您可以使用API中提供的以下树单元编辑器:CheckBoxTreeCellChoiceBoxTreeCellComboBoxTreeCellTextFieldTreeCell。这些类扩展了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中显示的输出,其中所有子项都被选中。

图14-7 依赖的CheckBoxTreeItem

图14-7的描述
"图14-7 依赖的CheckBoxTreeItem"的描述

要使CheckBoxTreeItem实例独立,使用setIndependent方法:rootItem.setIndependent(true);

当您运行TreeViewSample应用程序时,其行为应如图14-8所示发生变化。

图14-8 独立的CheckBoxTreeItem

图14-8的描述
"图14-8 独立的CheckBoxTreeItem"的描述

TreeTableView控件提供了在表格形式中呈现树结构的附加功能。有关这些控件的更多信息,请参见Tree Table View章节。

相关文档 

关闭窗口

目录

JavaFX:使用JavaFX UI组件

展开 折叠