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

1- TableView

JavaFX cung cấp class TableView, nó được sử dụng cùng với TableColumnTableCell giúp bạn hiển thị dữ liệu dưới dạng bảng (Tabular form).

2- Thêm cột vào TableView

Ví dụ dưới đây tạo một TableView, với các cột. Một cột có thể chứa các cột con.
TableViewDemo.java
package org.o7planning.javafx.tableview;

import org.o7planning.javafx.model.UserAccount;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class TableViewDemo extends Application {

  @Override
  public void start(Stage stage) {

      TableView<UserAccount> table = new TableView<UserAccount>();


      // Tạo cột UserName (Kiểu dữ liệu String)
      TableColumn<UserAccount, String> userNameCol //
              = new TableColumn<UserAccount, String>("User Name");

      // Tạo cột Email (Kiểu dữ liệu String)
      TableColumn<UserAccount, String> emailCol//
              = new TableColumn<UserAccount, String>("Email");

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

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

      TableColumn<UserAccount, String> lastNameCol //
              = new TableColumn<UserAccount, String>("Last Name");

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

      // Active Column
      TableColumn<UserAccount, Boolean> activeCol//
              = new TableColumn<UserAccount, Boolean>("Active");

      table.getColumns().addAll(userNameCol, emailCol, fullNameCol, activeCol);

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

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

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

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

public class UserAccount {

   private Long id;
   private String userName;
   private String email;
   private String firstName;
   private String lastName;
   private boolean active;

   public UserAccount(Long id, String userName, String email, //
           String firstName, String lastName, boolean active) {
       this.id = id;
       this.userName = userName;
       this.email = email;
       this.firstName = firstName;
       this.lastName = lastName;
       this.active = active;
   }

   public Long getId() {
       return id;
   }

   public void setId(Long id) {
       this.id = id;
   }

   public String getUserName() {
       return userName;
   }

   public void setUserName(String userName) {
       this.userName = userName;
   }

   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 boolean isActive() {
       return active;
   }

   public void setActive(boolean active) {
       this.active = active;
   }

}
Chạy ví dụ:

3- TableView với dữ liệu

Ví dụ dưới đây minh họa một TableView với dữ liệu. Bạn có thể sét đặt các cột có thể sắp xếp hoặc không.
TableViewDemo2.java
package org.o7planning.javafx.tableview;

import org.o7planning.javafx.model.UserAccount;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class TableViewDemo2 extends Application {

  @Override
  public void start(Stage stage) {

      TableView<UserAccount> table = new TableView<UserAccount>();


      // Tạo cột UserName (Kiểu dữ liệu String)
      TableColumn<UserAccount, String> userNameCol //
              = new TableColumn<UserAccount, String>("User Name");

      // Tạo cột Email (Kiểu dữ liệu String)
      TableColumn<UserAccount, String> emailCol//
              = new TableColumn<UserAccount, String>("Email");

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


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

      TableColumn<UserAccount, String> lastNameCol //
              = new TableColumn<UserAccount, String>("Last Name");

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

      // Active Column
      TableColumn<UserAccount, Boolean> activeCol//
              = new TableColumn<UserAccount, Boolean>("Active");

 
      // Đị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 UserAccount.
      userNameCol.setCellValueFactory(new PropertyValueFactory<>("userName"));
      emailCol.setCellValueFactory(new PropertyValueFactory<>("email"));
      firstNameCol.setCellValueFactory(new PropertyValueFactory<>("firstName"));
      lastNameCol.setCellValueFactory(new PropertyValueFactory<>("lastName"));
      activeCol.setCellValueFactory(new PropertyValueFactory<>("active"));
   

      // Sét xắp xếp theo userName
      userNameCol.setSortType(TableColumn.SortType.DESCENDING);
      lastNameCol.setSortable(false);


      // Hiển thị các dòng dữ liệu
      ObservableList<UserAccount> list = getUserList();
      table.setItems(list);

      table.getColumns().addAll(userNameCol, emailCol, fullNameCol, activeCol);

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

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

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

  private ObservableList<UserAccount> getUserList() {

      UserAccount user1 = new UserAccount(1L, "smith", "smith@gmail.com", //
              "Susan", "Smith", true);
      UserAccount user2 = new UserAccount(2L, "mcneil", "mcneil@gmail.com", //
              "Anne", "McNeil", true);
      UserAccount user3 = new UserAccount(3L, "white", "white@gmail.com", //
              "Kenvin", "White", false);

      ObservableList<UserAccount> list = FXCollections.observableArrayList(user1, user2, user3);
      return list;
  }

  public static void main(String[] args) {
      launch(args);
  }
}
Chạy ví dụ:

4- Chỉnh sửa dữ liệu trên Table

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

setCellFactory & setCellValueFactory

Bạn cần cung cấp cách hiển thị dữ liệu vào một ô thông qua phương thức tableColumn.setCellValueFactory. Và định nghĩa một thành phần (component) (và dữ liệu của thành phần này) sẽ hiển thị khi ô được hiệu chỉnh, thông qua phương thức tableColumn.setCellFactory.

onCellEditComit

Tiếp theo, bạn cần định nghĩa cách dữ liệu mới sẽ được cập nhập vào Model, thông qua phương thức tableColumn.setOnEditCommit. Sau khi hiệu chỉnh trên ô của bảng dữ liệu mới sẽ được cập nhập vào Model.
Với các ô hiển thị CheckBox trên TableView:
Lưu ý rằng các CheckBoxTableCell 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ụ:
TableViewEditDemo.java
package org.o7planning.javafx.tableview;

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

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.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.ComboBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class TableViewEditDemo extends Application {

   @Override
   public void start(Stage stage) {

       TableView<Person> table = new TableView<Person>();
 
       // Cho phép sửa trên bảng.
       table.setEditable(true);

       TableColumn<Person, String> fullNameCol //
               = new TableColumn<Person, String>("Full Name");

       TableColumn<Person, Gender> genderCol//
               = new TableColumn<Person, Gender>("Gender");

       TableColumn<Person, Boolean> singleCol//
               = new TableColumn<Person, Boolean>("Single?");

       // ==== FULL NAME (TEXT FIELD) ===
       fullNameCol.setCellValueFactory(new PropertyValueFactory<>("fullName"));

       fullNameCol.setCellFactory(TextFieldTableCell.<Person> forTableColumn());

       fullNameCol.setMinWidth(200);
 
       // Khi edit xong 1 ô ở cột FullName
       fullNameCol.setOnEditCommit((CellEditEvent<Person, String> event) -> {
           TablePosition<Person, String> pos = event.getTablePosition();

           String newFullName = event.getNewValue();

           int row = pos.getRow();
           Person person = event.getTableView().getItems().get(row);

           person.setFullName(newFullName);
       });

       // ==== GENDER (COMBO BOX) ===

       ObservableList<Gender> genderList = FXCollections.observableArrayList(//
               Gender.values());

       genderCol.setCellValueFactory(new Callback<CellDataFeatures<Person, Gender>, ObservableValue<Gender>>() {

           @Override
           public ObservableValue<Gender> call(CellDataFeatures<Person, Gender> param) {
               Person person = param.getValue();
               // F,M
               String genderCode = person.getGender();
               Gender gender = Gender.getByCode(genderCode);
               return new SimpleObjectProperty<Gender>(gender);
           }
       });

       genderCol.setCellFactory(ComboBoxTableCell.forTableColumn(genderList));

       genderCol.setOnEditCommit((CellEditEvent<Person, Gender> event) -> {
           TablePosition<Person, Gender> pos = event.getTablePosition();

           Gender newGender = event.getNewValue();

           int row = pos.getRow();
           Person person = event.getTableView().getItems().get(row);

           person.setGender(newGender.getCode());
       });

       genderCol.setMinWidth(120);

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

           @Override
           public ObservableValue<Boolean> call(CellDataFeatures<Person, Boolean> param) {
               Person person = param.getValue();

               SimpleBooleanProperty booleanProp = new SimpleBooleanProperty(person.isSingle());

 
               // Chú ý: singleCol.setOnEditCommit(): Không làm việc với
               // CheckBoxTableCell.
 
               // Khi cột "Single?" thay đổi
               booleanProp.addListener(new ChangeListener<Boolean>() {

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

       singleCol.setCellFactory(new Callback<TableColumn<Person, Boolean>, //
       TableCell<Person, Boolean>>() {
           @Override
           public TableCell<Person, Boolean> call(TableColumn<Person, Boolean> p) {
               CheckBoxTableCell<Person, Boolean> cell = new CheckBoxTableCell<Person, Boolean>();
               cell.setAlignment(Pos.CENTER);
               return cell;
           }
       });

       ObservableList<Person> list = getPersonList();
       table.setItems(list);

       table.getColumns().addAll(fullNameCol, genderCol, singleCol);

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

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

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

   private ObservableList<Person> getPersonList() {

       Person person1 = new Person("Susan Smith", Gender.FEMALE.getCode(), true);
       Person person2 = new Person("Anne McNeil", Gender.FEMALE.getCode(), true);
       Person person3 = new Person("Kenvin White", Gender.MALE.getCode(), false);

       ObservableList<Person> list = FXCollections.observableArrayList(person1, person2, person3);
       return list;
   }

   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;
   }

}
Person.java
package org.o7planning.javafx.model;

public class Person {
   
    private String fullName;
    private String gender;
    private boolean single;

    public Person(String fullName, String gender, boolean single) {
        this.fullName = fullName;
        this.gender = gender;
        this.single = single;
    }

    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    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;
    }
}