Hướng dẫn sử dụng JavaFX TreeTableView
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- JavaFX TreeTableView

JavaFX cung cấp cho bạn class TreeTableView, nó được sử dụng cùng với TreeItem, TreeTableColumnTreeTableCell giúp bạn hiển thị dữ liệu dưới dạng bảng (Tabular) đồng thời dưới dạng cây. Bạn có thể xem hình minh họa dưới đây:
Giống với TableView bạn có thể tạo ra các cột lồng nhau. Để tạo một TreeTableView bạn cần:
  1. Thêm các TreeItem vào TreeTableView để có được một cấu trúc cây.
  2. Định nghĩa các cột của bảng.
  3. Định nghĩa cách hiển thị dữ liệu trên mỗi ô thông qua phương thức TreeTableColumn.setCellValueFactory.

2- Ví dụ với TreeTableView

Tạo một đối tượng TreeTableView và thêm các cột ( TreeTableColumn).
TreeTableView<Employee> treeTableView = new TreeTableView<Employee>();

// Tạo cột EmpNo (Kiểu dữ liệu String)
TreeTableColumn<Employee, String> empNoCol //
      = new TreeTableColumn<Employee, String>("Emp No");

.....
 
// Thêm các cột vào TreeTable
treeTableView.getColumns().addAll(empNoCol, fullNameCol,
               positionCol, genderCol, singleCol);
Tạo các TreeItem và thêm vào TreeTableView để có một cấu trúc cây:
// Thêm dữ liệu
Employee empBoss = new Employee("E00", "Abc@gmail.com", //
      "Boss", "Boss", "Manager", "M", false);

Employee empSmith = new Employee("E01", "Smith@gmail.com", //
      "Susan", "Smith", "Salesman", "F", true);

Employee empMcNeil = new Employee("E02", "McNeil@gmail.com", //
      "Anne", "McNeil", "Cleck", "M", false);

// Phần tử gốc
TreeItem<Employee> itemRoot = new TreeItem<Employee>(empBoss);
TreeItem<Employee> itemSmith = new TreeItem<Employee>(empSmith);
TreeItem<Employee> itemMcNeil = new TreeItem<Employee>(empMcNeil);

itemRoot.getChildren().addAll(itemSmith, itemMcNeil);

// Sét đặt phần tử gốc cho cây.
treeTableView.setRoot(itemRoot);
Định nghĩa cách hiển thị dữ liệu trên mỗi ô của cây.
// Định nghĩa cách để lấy dữ liệu cho mỗi ô.
// Lấy giá trị từ các thuộc tính của Employee.

empNoCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("empNo"));
firstNameCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("firstName"));
lastNameCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("lastName"));
positionCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("position"));
genderCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("gender"));
singleCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, Boolean>("single"));
Xem ví dụ đầy đủ:
TreeTableViewDemo.java
package org.o7planning.javafx.treetableview;

import org.o7planning.javafx.model.Employee;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class TreeTableViewDemo extends Application {

   @Override
   public void start(Stage stage) {

       TreeTableView<Employee> treeTableView = new TreeTableView<Employee>();
 
       // Tạo cột EmpNo (Kiểu dữ liệu String)
       TreeTableColumn<Employee, String> empNoCol //
               = new TreeTableColumn<Employee, String>("Emp No");
 
       // Tạo cột FullName (Kiểu dữ liệu String)
       TreeTableColumn<Employee, String> fullNameCol//
               = new TreeTableColumn<Employee, String>("Full Name");
 
       // Tạo 2 cột con cho cột FullName
       TreeTableColumn<Employee, String> firstNameCol //
               = new TreeTableColumn<Employee, String>("First Name");

       TreeTableColumn<Employee, String> lastNameCol //
               = new TreeTableColumn<Employee, String>("Last Name");

 
       // Thêm 2 cột con vào cột FullName
       fullNameCol.getColumns().addAll(firstNameCol, lastNameCol);

       // Gender Column
       TreeTableColumn<Employee, String> genderCol //
               = new TreeTableColumn<Employee, String>("Gender");

       // Position Column
       TreeTableColumn<Employee, String> positionCol //
               = new TreeTableColumn<Employee, String>("Position");

       // Single? Column
       TreeTableColumn<Employee, Boolean> singleCol//
               = new TreeTableColumn<Employee, Boolean>("Single?");
 
       // Định nghĩa cách để lấy dữ liệu cho mỗi ô.
       // Lấy giá trị từ các thuộc tính của Employee.
       empNoCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("empNo"));
       firstNameCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("firstName"));
       lastNameCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("lastName"));
       positionCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("position"));
       genderCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("gender"));
       singleCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, Boolean>("single"));
 
       // Thêm các cột vào TreeTable
       treeTableView.getColumns().addAll(empNoCol, fullNameCol,positionCol, genderCol, singleCol);
 
       // Thêm dữ liệu
       Employee empBoss = new Employee("E00", "Abc@gmail.com", //
               "Boss", "Boss", "Manager", "M", false);

       Employee empSmith = new Employee("E01", "Smith@gmail.com", //
               "Susan", "Smith", "Salesman", "F", true);

       Employee empMcNeil = new Employee("E02", "McNeil@gmail.com", //
               "Anne", "McNeil", "Cleck", "M", false);

       // Phần tử gốc
       TreeItem<Employee> itemRoot = new TreeItem<Employee>(empBoss);
       TreeItem<Employee> itemSmith = new TreeItem<Employee>(empSmith);
       TreeItem<Employee> itemMcNeil = new TreeItem<Employee>(empMcNeil);

       itemRoot.getChildren().addAll(itemSmith, itemMcNeil);
       treeTableView.setRoot(itemRoot);
       //
       StackPane root = new StackPane();
       root.setPadding(new Insets(5));
       root.getChildren().add(treeTableView);

       stage.setTitle("TreeTableView (o7planning.org)");

       Scene scene = new Scene(root, 450, 300);
       stage.setScene(scene);
       stage.show();
   }

   public static void main(String[] args) {
       launch(args);
   }
}
Employee.java
package org.o7planning.javafx.model;

public class Employee {

    private String empNo;
    private String firstName;
    private String lastName;
    private String email;
    private String position;
    private String gender;

    private boolean single;

    public Employee(String empNo, String email, //
            String firstName, String lastName, String position, String gender, boolean single) {
        this.empNo = empNo;
        this.email = email;
        this.firstName = firstName;
        this.lastName = lastName;
        this.position = position;
        this.gender = gender;
        this.single = single;
    }

    public String getEmpNo() {
        return empNo;
    }

    public void setEmpNo(String empNo) {
        this.empNo = empNo;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getPosition() {
        return position;
    }

    public void setPosition(String position) {
        this.position = position;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public boolean isSingle() {
        return single;
    }

    public void setSingle(boolean single) {
        this.single = single;
    }

}

3- Sửa dữ liệu trên TreeTableView

Bạn có thể hiệu chỉnh trực tiếp trên TreeTableView, dữ liệu sẽ được update vào Model. Hình ảnh dưới đây minh họa một TreeTableView có thể hiệu chỉnh.

setCellFactory & setCellValueFactory

treeTableColumn.setCellValueFactory
  • Là phương thức sét đặt cách để lấy dữ liệu, mà dữ liệu đó sẽ hiển thị trên 1 ô của TreeTableView.
treeTableColumn.setCellFactory
  • Là phương thức sét đặt cách vẽ ra (render) một điều khiển (Controls) khi người dùng đang sửa dữ liệu trên 1 ô.

onCellEditComit

Tiếp theo bạn cần định nghĩa ra cách mà dữ liệu mới sẽ được cập nhập vào Model, bằng cách sử dụng phương thức treeTableColumn.setOnEditCommit. Sau khi người dùng hiệu chỉnh xong ô trên TreeTableView dữ liệu mới sẽ được cập nhập vào Model.
Với các ô hiển thị CheckBox trên TreeTableView:
Lưu ý rằng các CheckBoxTreeTableCell vẽ ra (render) CheckBox 'sống', có nghĩa là các CheckBox là luôn luôn tương tác và có thể chọn hoặc bỏ chọn trực tiếp bởi người sử dụng. Điều này có nghĩa rằng nó không phải là cần thiết chuyển sang trạng thái hiệu chỉnh của ô (thường để chuyển trạng thái hiệu chỉnh ô người dùng kích đúp vào ô). Một tác dụng phụ của việc này là các callbacks chỉnh sửa thông thường (chẳng hạn như onCommitEdit) sẽ không được gọi. Nếu bạn muốn được thông báo về những thay đổi, bạn nên làm việc với các bộ quan sát (Observe) thuộc tính boolean được chế tác bởi các CheckBox.
Ví dụ đầy đủ:
TreeTableViewEditDemo.java
package org.o7planning.javafx.treetableview;

import org.o7planning.javafx.model.Employee;
import org.o7planning.javafx.model.Gender;

import javafx.application.Application;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.CheckBoxTreeTableCell;
import javafx.scene.control.cell.ComboBoxTreeTableCell;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class TreeTableViewEditDemo extends Application {

   @Override
   public void start(Stage stage) {

       TreeTableView<Employee> treeTableView = new TreeTableView<Employee>();
       treeTableView.setEditable(true);

 
       // Tạo cột EmpNo (Kiểu dữ liệu String)
       TreeTableColumn<Employee, String> empNoCol //
               = new TreeTableColumn<Employee, String>("Emp No");

 
       // Tạo cột FullName (Kiểu dữ liệu String)
       TreeTableColumn<Employee, String> fullNameCol//
               = new TreeTableColumn<Employee, String>("Full Name");

 
       // Tạo 2 cột con cho cột FullName
       TreeTableColumn<Employee, String> firstNameCol //
               = new TreeTableColumn<Employee, String>("First Name");

       TreeTableColumn<Employee, String> lastNameCol //
               = new TreeTableColumn<Employee, String>("Last Name");
 
       // Thêm 2 cột con vào cột FullName
       fullNameCol.getColumns().addAll(firstNameCol, lastNameCol);

       // Gender Column
       TreeTableColumn<Employee, Gender> genderCol //
               = new TreeTableColumn<Employee, Gender>("Gender");
       genderCol.setMinWidth(90);

       // Position Column
       TreeTableColumn<Employee, String> positionCol //
               = new TreeTableColumn<Employee, String>("Position");

       // Single? Column
       TreeTableColumn<Employee, Boolean> singleCol//
               = new TreeTableColumn<Employee, Boolean>("Single?");

 
       // Thêm các cột vào TreeTable
       treeTableView.getColumns().addAll(empNoCol, fullNameCol, positionCol, genderCol, singleCol);
 
       // Thêm dữ liệu
       Employee empBoss = new Employee("E00", "Abc@gmail.com", //
               "Boss", "Boss", "Manager", "M", false);

       Employee empSmith = new Employee("E01", "Smith@gmail.com", //
               "Susan", "Smith", "Salesman", "F", true);

       Employee empMcNeil = new Employee("E02", "McNeil@gmail.com", //
               "Anne", "McNeil", "Cleck", "M", false);
 
       // Phần tử gốc
       TreeItem<Employee> itemRoot = new TreeItem<Employee>(empBoss);
       TreeItem<Employee> itemSmith = new TreeItem<Employee>(empSmith);
       TreeItem<Employee> itemMcNeil = new TreeItem<Employee>(empMcNeil);

       itemRoot.getChildren().addAll(itemSmith, itemMcNeil);
       treeTableView.setRoot(itemRoot);
 
       // Định nghĩa cách để lấy dữ liệu cho mỗi ô.
       // Lấy giá trị từ các thuộc tính của Employee.
       empNoCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("empNo"));
       firstNameCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("firstName"));
       lastNameCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("lastName"));
       positionCol.setCellValueFactory(new TreeItemPropertyValueFactory<Employee, String>("position"));

       // GENDER (COMBO BOX).
       genderCol.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<Employee, Gender>, //
       ObservableValue<Gender>>() {

           @Override
           public ObservableValue<Gender> call(TreeTableColumn.CellDataFeatures<Employee, Gender> param) {
               TreeItem<Employee> treeItem = param.getValue();
               Employee emp = treeItem.getValue();
               // F,M
               String genderCode = emp.getGender();
               Gender gender = Gender.getByCode(genderCode);
               return new SimpleObjectProperty<Gender>(gender);
           }
       });
       ObservableList<Gender> genderList = FXCollections.observableArrayList(//
               Gender.values());
       genderCol.setCellFactory(ComboBoxTreeTableCell.forTreeTableColumn(genderList));

 
       // Sau khi người dùng sửa dữ liệu trên ô cần update vào Model.
       genderCol.setOnEditCommit(new EventHandler<TreeTableColumn.CellEditEvent<Employee, Gender>>() {

           @Override
           public void handle(TreeTableColumn.CellEditEvent<Employee, Gender> event) {
               TreeItem<Employee> item = event.getRowValue();
               Employee emp = item.getValue();
               Gender newGender = event.getNewValue();
               emp.setGender(newGender.getCode());
               System.out.println("Single column commit. new gender:" +newGender);
               System.out.println("EMP:"+emp.isSingle());
           }
       });

       // ==== SINGLE? (CHECH BOX) ===
       singleCol.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<Employee, Boolean>, //
       ObservableValue<Boolean>>() {

           @Override
           public ObservableValue<Boolean> call(TreeTableColumn.CellDataFeatures<Employee, Boolean> param) {
               TreeItem<Employee> treeItem = param.getValue();
               Employee emp = treeItem.getValue();
               SimpleBooleanProperty booleanProp= new SimpleBooleanProperty(emp.isSingle());
             
               // Chú ý: singleCol.setOnEditCommit(): Không làm việc với
               // CheckBoxTreeTableCell.
               // Khi cột "Single?" thay đổi              
               booleanProp.addListener(new ChangeListener<Boolean>() {

                   @Override
                   public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
                           Boolean newValue) {
                       emp.setSingle(newValue);
                   }                    
               });
               return booleanProp;
           }
       });

     
        singleCol.setCellFactory(new Callback<TreeTableColumn<Employee,Boolean>,TreeTableCell<Employee,Boolean>>() {
           @Override
           public TreeTableCell<Employee,Boolean> call( TreeTableColumn<Employee,Boolean> p ) {
               CheckBoxTreeTableCell<Employee,Boolean> cell = new CheckBoxTreeTableCell<Employee,Boolean>();
               cell.setAlignment(Pos.CENTER);
               return cell;
           }
       });

       //
       StackPane root = new StackPane();
       root.setPadding(new Insets(5));
       root.getChildren().add(treeTableView);

       stage.setTitle("TreeTableView (o7planning.org)");

       Scene scene = new Scene(root, 450, 300);
       stage.setScene(scene);
       stage.show();
   }

   public static void main(String[] args) {
       launch(args);
   }
}
Gender.java
package org.o7planning.javafx.model;

public enum Gender {

    FEMALE("F", "Famale"), MALE("M", "Male");

    private String code;
    private String text;

    private Gender(String code, String text) {
        this.code = code;
        this.text = text;
    }

    public String getCode() {
        return code;
    }

    public String getText() {
        return text;
    }

    public static Gender getByCode(String genderCode) {
        for (Gender g : Gender.values()) {
            if (g.code.equals(genderCode)) {
                return g;
            }
        }
        return null;
    }

    @Override
    public String toString() {
        return this.text;
    }

}