javaFX学习之TreeView组件(转载)

转载:http://www.javafxchina.net/blog/2015/04/doc03_treeview/

javaFX应用程序中如何构建树形结构,如何向树视图中增加项、处理事件、通过实现和添加单元格工厂(Cell Factory)自定义树的单元格。

在javafx.scene.control包中的TreeView类提供了展示层级结构的视图。在树中最顶级的节点被称为“根(Root)节点”。Root节点包括了一些子节点,这些子节点还可以有下级子节点。一个没有子节点的节点被称为“叶子(leaf)节点

是一个带有树视图的应用程序截图

创建树视图

如果需要在JavaFX应用中创建树形结构,你一般需要实例化TreeView类,然后定义一些TreeItem对象,指定其中的某一个成为根节点,将根节点添加到树视图中,并将其它TreeItem对象添加到根节点下。

通过调用TreeItem对应的构造方法或使用setGraphic方法,你可以为每个树节点增加图标。建议的图标大小是16 x16,不过实际上任何Node对象都可以被设置为图标,并且将会有全部的交互能力

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 TreeViewTest extends Application {

    private final Node rootIcon = new ImageView(
            new Image(getClass().getResourceAsStream("folder_16.jpg"))
    );//定义一个图片类型节点对象

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("TreeView例子");//舞台标题

        TreeItem<String> rootItem = new TreeItem<> ("Inbox", 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循环中创建的的TreeItem都通过调用getChildren和add方法被添加到了root节点上。你也可以使用addAll方法来一次性添加这些子节点。

在上面的代码样例中,你可以通过TreeView类的构造方法来设置根节点,或者通过setRoot方法来指定。

在根节点对象上对setExpanded方法的调用定义了树视图的初始外观。默认情况下所有的TreeItem实例是被折叠起来的,并且必须在必要时手工展开。如果在setExpanded方法调用时设置参数为true,这样会在程序启动时会自动展开根节点

在样例代码中创建了一个简单的树视图,它带有String类型的节点。不过,树型结构可以包含带有各种不同类型数据的节点。使用带有泛型的TreeItem构造方法可以定义展现特定类型数据的树:TreeItem<T> (T value)。T类型的value可以指定任何对象,例如UI控件或者自定义组件等。

与TreeView类不同,TreeItem类不是继承自Node类。因此,你不能对树节点增加视觉特效或者菜单。使用Cell Factory机制可以克服这个障碍,并根据你的应用程序所需来自定义树节点的行为。

实现单元工厂(Cell Factory)

Cell Factory机制用于产生TreeCell实例来在TreeView中表现单个的TreeItem。当你的应用需要操作大量动态改变或增加的数据时使用Cell Factory会非常有用。

设想这样一个应用程序:它需要展现某公司的人力资源信息,并且允许用户修改雇员信息、增加新的雇员。

 下面的样例代码创建了Employee类,并对雇员根据部门分组

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
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.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import java.util.Arrays;
import java.util.List;

public class TreeViewNodeTest extends Application {

    private final Node rootIcon =
            new ImageView(new Image(getClass().getResourceAsStream("folder_16.jpg")));//创建一个图片节点类型对象
    private final Image depIcon =
            new Image(getClass().getResourceAsStream("folder_16.jpg"));//创建一个图片节点类型对象
    List<Employee> employees = Arrays.<Employee>asList(
            new Employee("Jacob Smith", "Accounts Department"),
            new Employee("Isabella Johnson", "Accounts Department"),
            new Employee("Ethan Williams", "Sales Department"),
            new Employee("Emma Jones", "Sales Department"),
            new Employee("Michael Brown", "Sales Department"),
            new Employee("Anna Black", "Sales Department"),
            new Employee("Rodger York", "Sales Department"),
            new Employee("Susan Collins", "Sales Department"),
            new Employee("Mike Graham", "IT Support"),
            new Employee("Judy Mayer", "IT Support"),
            new Employee("Gregory Smith", "IT Support"));//创建员工列表对象
    TreeItem<String> rootNode;//根节点选项元素

    public static void main(String[] args) {
        launch(args);
    }

    public TreeViewNodeTest() {
        this.rootNode = new TreeItem<>("根节点", rootIcon);
    }//初始化树形结构根节点的方法

    @Override
    public void start(Stage stage) {
        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);//部门节点上添加员工节点
            }
        }

        stage.setTitle("树形结构标例");
        VBox box = new VBox();//创建垂直盒子模型
        final Scene scene = new Scene(box, 400, 300);//创建场景对象
        scene.setFill(Color.LIGHTGRAY);//场景填充背景色

        TreeView<String> treeView = new TreeView<>(rootNode);//给树形结构添加根节点

        box.getChildren().add(treeView);//垂直布局对象上面添加一个树形结构节点
        stage.setScene(scene);//舞台设置场景
        stage.show();//舞台播放
    }

    public static class Employee {//员工pojo类型

        private final SimpleStringProperty name;//mvvm机制下的和String很像的数据类型SimpleStringProperty
        private final SimpleStringProperty department;

        private Employee(String name, String department) {//pojo对象类型的构造方法
            this.name = new SimpleStringProperty(name);
            this.department = new SimpleStringProperty(department);
        }

        public String getName() {
            return name.get();
        }

        public void setName(String fName) {
            name.set(fName);
        }

        public String getDepartment() {
            return department.get();
        }

        public void setDepartment(String fName) {
            department.set(fName);
        }
    }
}

每个Employee对象具有两个属性:name和department。与雇员信息对应的TreeItem对象被用作树的叶子节点,与部门信息对应的TreeItem被用作带有子节点树节点。新部门的名称从Emploee对象中通过getDepartment获取

在上面的例子中,你可以预览树视图及其内容,但是你不能修改或增加其中的项。下面的例子中实现了Cell Factory,提供了修改功能,你可以修改雇员的姓名

import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.layout.VBox;

public class TreeViewUpdate extends Application {

    private final Node rootIcon =
            new ImageView(new Image(getClass().getResourceAsStream("folder_16.jpg")));//创建图片节点
    private final Image depIcon =
            new Image(getClass().getResourceAsStream("folder_16.jpg"));//创建图片节点,部门图片节点
    List<Employee> employees = Arrays.<Employee>asList(
            new Employee("Jacob Smith", "Accounts Department"),
            new Employee("Isabella Johnson", "Accounts Department"),
            new Employee("Ethan Williams", "Sales Department"),
            new Employee("Emma Jones", "Sales Department"),
            new Employee("Michael Brown", "Sales Department"),
            new Employee("Anna Black", "Sales Department"),
            new Employee("Rodger York", "Sales Department"),
            new Employee("Susan Collins", "Sales Department"),
            new Employee("Mike Graham", "IT Support"),
            new Employee("Judy Mayer", "IT Support"),
            new Employee("Gregory Smith", "IT Support"));//创建员工信息数据集合
    TreeItem<String> rootNode;//树形结构选项

    public static void main(String[] args) {
        Application.launch(args);
    }

    public TreeViewUpdate() {
        this.rootNode = new TreeItem<>("根节点元素", rootIcon);//创建根节点选项元素对象
    }

    @Override
    public void start(Stage stage) {
        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);//部门节点
            }
        }

        stage.setTitle("TreeView更新节点测试");
        VBox box = new VBox();//创建垂直盒子布局器
        final Scene scene = new Scene(box, 800, 600);//创建一个场景对象
        scene.setFill(Color.LIGHTGRAY);//设置场景背景颜色

        TreeView<String> treeView = new TreeView<>(rootNode);//创建一个树形结构根节点
        treeView.setEditable(true);//将树形结构对象设置为可编辑
        treeView.setCellFactory((TreeView<String> p) ->
                new TextFieldTreeCellImpl());

        box.getChildren().add(treeView);
        stage.setScene(scene);
        stage.show();
    }

    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对象
            textField.selectAll();//选中文本域对象使其获得焦点
        }

        @Override
        public void cancelEdit() {//重写继承自TreeCell类的模板方法
            super.cancelEdit();//取消编辑内容文本
            setText((String) getItem());//设置文本内容为所选条目
            setGraphic(getTreeItem().getGraphic());//设置去小时候的所选图形外观对象为树形结构所选的图形外观组件
        }

        @Override
        public void updateItem(String item, boolean empty) {//更新选项(重写继承自TreeCell类的模板方法)
            super.updateItem(item, empty);//更新树形结构选项,调用父类的模板方法

            if (empty) {
                setText(null);//设置文本元素为空
                setGraphic(null);//设置图形元素为空
            } else {
                if (isEditing()) {//如果正在编辑
                    if (textField != null) {
                        textField.setText(getString());//将前文定义的文本域内容设置为从编辑的cell组件中获取的文本内容
                    }
                    setText(null);//设置文本内容为null
                    setGraphic(textField);//设置图形外观为前文定义的textField类型元素对象
                } else {
                    setText(getString());//设置文本内容
                    setGraphic(getTreeItem().getGraphic());//设置外观图形组件为所选树形结构中的图形外观元素
                }
            }
        }

        private void createTextField() {//创建文本域对象
            textField = new TextField(getString());//创建文本域,初始化文本域对象的文本内容为从cell组件中取文本值
            textField.setOnKeyReleased((KeyEvent t) -> {//针对本文本域对象的键盘释放时间处理回调函数的编写
                if (t.getCode() == KeyCode.ENTER) {//判断当前敲击的键如果是回车键
                    commitEdit(textField.getText());//则调用继承自父类TreeCell类型的提交编辑改变内容方法
                } else if (t.getCode() == KeyCode.ESCAPE) {//判断当前键盘事件中获取当前使用键盘的代码是否是ESC键
                    cancelEdit();//调用取消编辑方法
                }
            });
        }

        private String getString() {//获取文本选项域对象的内容,利用继承自Cell父类的getItem()选项方法获取
            return getItem() == null ? "" : getItem().toString();
        }
    }

    public static class Employee {//创建pojo类型对象

        private final SimpleStringProperty name;//同String类型值去用
        private final SimpleStringProperty department;//同String类型值去用

        private Employee(String name, String department) {//pojo类的构造方法
            this.name = new SimpleStringProperty(name);
            this.department = new SimpleStringProperty(department);
        }

        public String getName() {
            return name.get();
        }

        public void setName(String fName) {
            name.set(fName);
        }

        public String getDepartment() {
            return department.get();
        }

        public void setDepartment(String fName) {
            department.set(fName);
        }
    }
}

 

通过调用treeView对象的setCellFactory方法可以覆盖TreeCell的默认实现,这里通过TextFieldTreeCellImpl类重新定义了子节点。

TextFieldTreeCellImpl类为每一个树节点创建了一个TextField对象并提供了处理编辑事件的方法。

注意你必须明确地调用TreeView的setEditable(true)方法来允许编辑其节点。

编译并运行上面的程序,然后尝试通过点击树上的雇员姓名来编辑之

 

 

添加新的树节点

 

 

修改上面的样例程序来使其具有增加新增雇员的功能。使用下面例子中被加粗的代码行来实现此功能。这些代码会为对应的部门节点增加一个上下文菜单。当选择“Add Employee”菜单时,新的Tree Item会被添加到当前的部门下作为叶子节点。

使用isLeaf方法来判断TreeItem是部门还是雇员(是否是叶子节点)

 添加新的树节点

import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.ActionEvent;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.VBox;

public class TreeViewEditAdd extends Application {

    private final Node rootIcon =
            new ImageView(new Image(getClass().getResourceAsStream("folder_16.jpg")));//创建根节点图形图像对象
    private final Image depIcon =
            new Image(getClass().getResourceAsStream("folder_16.jpg"));//创建部门节点的外观图片
    List<Employee> employees = Arrays.<Employee>asList(
            new Employee("Jacob Smith", "Accounts Department"),
            new Employee("Isabella Johnson", "Accounts Department"),
            new Employee("Ethan Williams", "Sales Department"),
            new Employee("Emma Jones", "Sales Department"),
            new Employee("Michael Brown", "Sales Department"),
            new Employee("Anna Black", "Sales Department"),
            new Employee("Rodger York", "Sales Department"),
            new Employee("Susan Collins", "Sales Department"),
            new Employee("Mike Graham", "IT Support"),
            new Employee("Judy Mayer", "IT Support"),
            new Employee("Gregory Smith", "IT Support"));//创建员工实体的List<Employee>类型对象
    TreeItem<String> rootNode = new TreeItem<>("根节点元素", rootIcon);//根节点选项元素的创建

    public static void main(String[] args) {
        Application.launch(args);//javaFx程序的启动
    }

    @Override
    public void start(Stage stage) {
        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 depNode = new TreeItem(employee.getDepartment(),
                        new ImageView(depIcon)
                );//部门节点选项的创建
                rootNode.getChildren().add(depNode);//根节点添加部门节点
                depNode.getChildren().add(empLeaf);//部门节点上添加叶子节点
            }
        }

        stage.setTitle("测试树形结构添加");
        VBox box = new VBox();//垂直布局盒子对象的创建
        final Scene scene = new Scene(box, 400, 300);//场景对象的创建
        scene.setFill(Color.LIGHTGRAY);//设置场景填充颜色

        TreeView<String> treeView = new TreeView<>(rootNode);//创建树形结构并挂载根节点选项
        treeView.setEditable(true);//设置树形结构可编辑
        treeView.setCellFactory((TreeView<String> p) ->
                new TextFieldTreeCellImpl());//将treeView对象的选项节点元素设置成自定义的TreeCell实现

        box.getChildren().add(treeView);//垂直盒子布局对象添加treeView组件
        stage.setScene(scene);//舞台设置场景
        stage.show();//舞台展现
    }

    private final class TextFieldTreeCellImpl extends TreeCell<String> {

        private TextField textField;//文本域外观组件对象
        private final ContextMenu addMenu = new ContextMenu();//上下文菜单对象的创建

        public TextFieldTreeCellImpl() {//构造方法的定义
            MenuItem addMenuItem = new MenuItem("添加节点");//上下文菜单选项对象的创建
            addMenu.getItems().add(addMenuItem);//将该选项添加至菜单
            addMenuItem.setOnAction((ActionEvent t) -> {//设置菜单选项组件对象的鼠标点击事件处理回调函数
                TreeItem newEmployee =
                        new TreeItem<>("New Employee");//创建一个树形结构选项节点选项对象
                getTreeItem().getChildren().add(newEmployee);//树形选项集合中添加树形选项节点对象
            });
        }

        @Override
        public void startEdit() {//实现继承自TreeCell类的同名模板方法
            super.startEdit();//调用父类同名开始编辑模板方法

            if (textField == null) {
                createTextField();//如果文本域对象为空则创建一个
            }
            setText(null);//设置文本内容
            setGraphic(textField);//设置编辑节点的图形元素为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);//若修改内容为空则选中节点内容设为null
                setGraphic(null);//设置选择的外观节点为null
            } else {
                if (isEditing()) {//判断是否是在编辑
                    if (textField != null) {//判断选择编辑的文本内容是否为null
                        textField.setText(getString());//若不为空,则设置文本域的文本内容为所选择的内容
                    }
                    setText(null);//设置文本内容为空
                    setGraphic(textField);//设置外观组件为前文定义的textField对象
                } else {
                    setText(getString());//设置TextFieldTreeCellImpl对象的文本内容为所选文本内容
                    setGraphic(getTreeItem().getGraphic());//设置TextFieldTreeCellImpl对象的外观图形为所选树形结构节点的图形外观
                    if (
                            !getTreeItem().isLeaf()&&getTreeItem().getParent()!= null//判断不是叶子节点且有父亲节点
                    ){
                        setContextMenu(addMenu);//设置上下文菜单添加菜单对象
                    }
                }
            }
        }

        private void createTextField() {
            textField = new TextField(getString());//创建文本域对象取值为TextFieldTreeCellImpl类型treeCell对象选择的文本内容
            textField.setOnKeyReleased((KeyEvent t) -> {//textField对象的键盘释放事件监听处理回调函数
                if (t.getCode() == KeyCode.ENTER) {//判断键盘事件对应的键位
                    commitEdit(textField.getText());//如果是回车键则提交文本区域内容作为树形结构节点选项的提交编辑内容
                } else if (t.getCode() == KeyCode.ESCAPE) {//当按的键盘键位是esc键就取消编辑功能
                    cancelEdit();
                }
            });

        }

        private String getString() {
            return getItem() == null ? "" : getItem().toString();
        }//利用继承的模板方法获取选项的选择文本内容
    }

    public static class Employee {//pojo类型的domain类型定义

        private final SimpleStringProperty name;//同String的用法
        private final SimpleStringProperty department;//同String的用法

        private Employee(String name, String department) {//构造函数的定义
            this.name = new SimpleStringProperty(name);
            this.department = new SimpleStringProperty(department);
        }

        public String getName() {
            return name.get();
        }

        public void setName(String fName) {
            name.set(fName);
        }

        public String getDepartment() {
            return department.get();
        }

        public void setDepartment(String fName) {
            department.set(fName);
        }
    }
}

 

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值