原文地址http://download.oracle.com/javafx/2.0/ui_controls/table-view.htm
JavaFX SDK API在的好幾個類都被設計來以表格形式呈現數據。在 JavaFX應用中創建表格的最重要類是TableView
, TableColumn
, 和TableCell
。可以通過實現數據模型或者應用一個細胞工廠來產生表格。
表格的類提供了內置的功能來在必要的時候進行數據排序和重置大小。
Figure 13-1 是一個典型的表格,用來呈現地址簿中的聯繫人信息。
創建Table
Example 13-1 中的代碼塊創建了一個空表格,它帶有3列。然後被加入了應用的場景中。
Example 13-1 Adding a Table
import javafx.application.Application; import javafx.geometry.Insets; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.layout.VBox; import javafx.scene.text.Font; import javafx.stage.Stage; public class Main extends Application { private TableView table = new TableView(); public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { Scene scene = new Scene(new Group()); stage.setTitle("Table View Sample"); stage.setWidth(400); stage.setHeight(500); final Label label = new Label("Address Book"); label.setFont(new Font("Arial", 20)); TableColumn firstNameCol = new TableColumn("First Name"); TableColumn lastNameCol = new TableColumn("Last Name"); TableColumn emailCol = new TableColumn("Email"); table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); final VBox vbox = new VBox(); vbox.setSpacing(5); vbox.getChildren().addAll(label, table); vbox.setPadding(new Insets(10, 0, 0, 10)); ((Group) scene.getRoot()).getChildren().addAll(vbox); stage.setScene(scene); stage.show(); } }
表格控件是通過實例化TableView
類創建的。在 Example 13-1 中,它被加入到了VBox
佈局容器中,然而,你可以直接把它加入應用場景中。
Example 13-1 定義了三列來存儲地址簿中的以下信息:某個聯繫人的名和姓還有電郵地址。列是用TableColumn
類創建的。
TableView
類的getColumns
方法把前面創建的列加入到表格中。在應用中,可以用這個方法動態的添加和移除列。
編譯運行的效果如下Figure 13-2 .
可以通過調用setVisible
方法來管理列的可視性。比如說,你應用的邏輯要求隱藏用戶電郵地址,可以這樣達到目的:emailCol.setVisible(false)
.
如果數據要求更復雜的數據呈現結構,可以創建內嵌的列。
比如,如果地址簿中的聯繫人有兩個email賬戶,就需要兩列來展示首選和次要地址了。創建兩個子列,然後在emailCol
上調用getColumns
方法,見 Example 13-2 .
Example 13-2 Creating Nested Columns
TableColumn firstEmailCol = new TableColumn("Primary"); TableColumn secondEmailCol = new TableColumn("Secondary"); emailCol.getColumns().addAll(firstEmailCol, secondEmailCol);
把這些代碼加入到 Example 13-1 , 然後編譯運行,表格的呈現效果如 Figure 13-3 .
儘管表格被加入到了應用中,標準標題依然顯示的是"No content in table" 因爲沒定義數據。爲了不顯示這個標題,可以使用setPlaceholder方法指定一個
Node
對象來顯示在空表格中。
定義Data Model
當在JavaFX應用中創建表格時,最佳實踐是實現一個定義了數據模型、提供了方法和字段的類來擴展表格的工作。Example 13-3 創建了一個Person類來定義地址簿中的數據。
Example 13-3 Creating the Person Class
public static class Person { private final SimpleStringProperty firstName; private final SimpleStringProperty lastName; private final SimpleStringProperty email; private Person(String fName, String lName, String email) { this.firstName = new SimpleStringProperty(fName); this.lastName = new SimpleStringProperty(lName); this.email = new SimpleStringProperty(email); } public String getFirstName() { return firstName.get(); } public void setFirstName(String fName) { firstName.set(fName); } public String getLastName() { return lastName.get(); } public void setLastName(String fName) { lastName.set(fName); } public String getEmail() { return email.get(); } public void setEmail(String fName) { email.set(fName); } }
firstName
, lastName
, 和email
字符串屬性(string property)是創建來引用特定的數據元素的。
另外,
get和
set方法是提供給每個數據元素的。這樣,比如說,
getFirstName方法返回了
firstName屬性的值,而
setFirstName
方法爲這個屬性指定了值。
當數據模型在 Person
類中形成時,可以創建一個ObservableList
數組來定義足夠多的行來在表格中顯示你的數據。看Example 13-4 中的代碼。
Example 13-4 Defining Table Data in an Observable List
final ObservableList<Person> data = FXCollections.observableArrayList( new Person("Jacob", "Smith", "[email protected]"), new Person("Isabella", "Johnson", "[email protected]"), new Person("Ethan", "Williams", "[email protected]"), new Person("Emma", "Jones", "[email protected]"), new Person("Michael", "Brown", "[email protected]") );
下一步是將數據和表格列相關聯。可以通過爲每個數據元素定義的屬性來實現,見Example 13-5 .
Example 13-5 Setting Data Properties to Columns
firstNameCol.setCellValueFactory( new PropertyValueFactory<Person,String>("firstName") ); lastNameCol.setCellValueFactory( new PropertyValueFactory<Person,String>("lastName") ); emailCol.setCellValueFactory( new PropertyValueFactory<Person,String>("email") );
setCellValueFactory
方法爲每列指定了一個細胞工廠。細胞工廠是通過使用PropertyValueFactory
類來實現的,該類使用了表格列的firstName
, lastName
和email
屬性來引用Person相應的方法。
定義了數據模型、加入數據並和列相關聯後可以把數據加入表格了。使用TableView
類的setItems
方法:table.setItems(data)
.
由於ObservableList對象可以跟蹤元素的任何改變,
TableView的內容在數據改變後是自動更新的。
查看 Example 13-6 中的代碼。
Example 13-6 Creating a Table and Adding Data to It
import javafx.beans.property.SimpleStringProperty; import javafx.scene.control.cell.PropertyValueFactory; import javafx.application.Application; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.layout.VBox; import javafx.scene.text.Font; import javafx.stage.Stage; public class Main extends Application { public static class Person { private final SimpleStringProperty firstName; private final SimpleStringProperty lastName; private final SimpleStringProperty email; private Person(String fName, String lName, String email) { this.firstName = new SimpleStringProperty(fName); this.lastName = new SimpleStringProperty(lName); this.email = new SimpleStringProperty(email); } public String getFirstName() { return firstName.get(); } public void setFirstName(String fName) { firstName.set(fName); } public String getLastName() { return lastName.get(); } public void setLastName(String fName) { lastName.set(fName); } public String getEmail() { return email.get(); } public void setEmail(String fName) { email.set(fName); } } private TableView<Person> table = new TableView<Person>(); private final ObservableList<Person> data = FXCollections.observableArrayList( new Person("Jacob", "Smith", "[email protected]"), new Person("Isabella", "Johnson", "[email protected]"), new Person("Ethan", "Williams", "[email protected]"), new Person("Emma", "Jones", "[email protected]"), new Person("Michael", "Brown", "[email protected]") ); public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { Scene scene = new Scene(new Group()); stage.setTitle("Table View Sample"); stage.setWidth(400); stage.setHeight(500); final Label label = new Label("Address Book"); label.setFont(new Font("Arial", 20)); TableColumn firstNameCol = new TableColumn("First Name"); firstNameCol.setCellValueFactory( new PropertyValueFactory<Person,String>("firstName") ); TableColumn lastNameCol = new TableColumn("Last Name"); lastNameCol.setCellValueFactory( new PropertyValueFactory<Person,String>("lastName") ); TableColumn emailCol = new TableColumn("Email"); emailCol.setMinWidth(200); emailCol.setCellValueFactory( new PropertyValueFactory<Person,String>("email") ); table.setItems(data); table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); final VBox vbox = new VBox(); vbox.setSpacing(5); vbox.getChildren().addAll(label, table); vbox.setPadding(new Insets(10, 0, 0, 10)); ((Group) scene.getRoot()).getChildren().addAll(vbox); stage.setScene(scene); stage.show(); } }
編譯運行的效果如圖 Figure 13-4 所示。
新增行
Figure 13-4 中的表格包含了5行,目前還無法更改。
可以使用文本框來輸入First Name, Last Name和 Email 列中的內容。Text Field控件使你的應用能夠接收用戶的輸入。Example 13-7創建了三個文本框並分別定義了提示語,還創建了一個Add按鈕。
Example 13-7 Using Text Fields to Enter New Items in the Table
final TextField addFirstName = new TextField(); addFirstName.setPromptText("Last Name"); addFirstName.setMaxWidth(firstNameCol.getPrefWidth()); final TextField addLastName = new TextField(); addLastName.setMaxWidth(lastNameCol.getPrefWidth()); addLastName.setPromptText("Last Name"); final TextField addEmail = new TextField(); addEmail.setMaxWidth(emailCol.getPrefWidth()); addEmail.setPromptText("Email"); final Button addButton = new Button("Add"); addButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent e) { data.add(new Person( addFirstName.getText(), addLastName.getText(), addEmail.getText() )); addFirstName.setText(""); addLastName.setText(""); addEmail.setText(""); } });
點擊 Add按鈕後,文本框中的值就包含進一個Person
的構造方法並加入到data可見列表(
observable list)中。這樣
,新輸入的聯繫人信息就顯示在表格中了。
查看 Example 13-8 中的代碼。
Example 13-8 Table with the Text Fields to Enter New Items
import javafx.application.Application; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.text.Font; import javafx.stage.Stage; public class Main extends Application { public static class Person { private final StringProperty firstName; private final StringProperty lastName; private final StringProperty email; private Person(String fName, String lName, String email) { this.firstName = new SimpleStringProperty(fName); this.lastName = new SimpleStringProperty(lName); this.email = new SimpleStringProperty(email); } public String getFirstName() { return firstName.get(); } public void setFirstName(String fName) { firstName.set(fName); } public String getLastName() { return lastName.get(); } public void setLastName(String fName) { lastName.set(fName); } public String getEmail() { return email.get(); } public void setEmail(String fName) { email.set(fName); } } private TableView<Person> table = new TableView<Person>(); private final ObservableList<Person> data = FXCollections.observableArrayList( new Person("Jacob", "Smith", "[email protected]"), new Person("Isabella", "Johnson", "[email protected]"), new Person("Ethan", "Williams", "[email protected]"), new Person("Emma", "Jones", "[email protected]"), new Person("Michael", "Brown", "[email protected]") ); private HBox hb = new HBox(); public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { Scene scene = new Scene(new Group()); stage.setTitle("Table View Sample"); stage.setWidth(400); stage.setHeight(500); final Label label = new Label("Address Book"); label.setFont(new Font("Arial", 20)); TableColumn firstNameCol = new TableColumn("First"); firstNameCol.setCellValueFactory( new PropertyValueFactory<Person,String>("firstName") ); TableColumn lastNameCol = new TableColumn("Last"); lastNameCol.setCellValueFactory( new PropertyValueFactory<Person,String>("lastName") ); TableColumn emailCol = new TableColumn("Email"); emailCol.setMinWidth(200); emailCol.setCellValueFactory( new PropertyValueFactory<Person,String>("email") ); table.setItems(data); table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); final TextField addFirstName = new TextField(); addFirstName.setPromptText("Last Name"); addFirstName.setMaxWidth(firstNameCol.getPrefWidth()); final TextField addLastName = new TextField(); addLastName.setMaxWidth(lastNameCol.getPrefWidth()); addLastName.setPromptText("Last Name"); final TextField addEmail = new TextField(); addEmail.setMaxWidth(emailCol.getPrefWidth()); addEmail.setPromptText("Email"); final Button addButton = new Button("Add"); addButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent e) { data.add(new Person( addFirstName.getText(), addLastName.getText(), addEmail.getText() )); addFirstName.setText(""); addLastName.setText(""); addEmail.setText(""); } }); hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton); hb.setSpacing(3); final VBox vbox = new VBox(); vbox.setSpacing(5); vbox.getChildren().addAll(label, table, hb); vbox.setPadding(new Insets(10, 0, 0, 10)); ((Group) scene.getRoot()).getChildren().addAll(vbox); stage.setScene(scene); stage.show(); } }
應用並沒有提供任何過濾器來檢查輸入(比如輸入的電郵地址並不符合正確形式)。 你可以在開發的時候自己加上這些功能。
當前應用也不能檢查十分輸入了空值。如果沒輸入內容,點擊Add按鈕會加入空行。
Figure 13-5 演示了用戶如何新增行。
Figure 13-5 Adding Contact Information to the Address Book
Description of "Figure 13-5 Adding Contact Information to the Address Book"
Figure 13-6 是上圖點擊Add按鈕後的效果。聯繫人Emma White的信息現在在表格中顯示了。
數據排序
TableView
類提供了內置的數據排序能力。。用戶可以點擊列標題來改變數據順序。點擊一次是遞增排序,點擊兩次是遞減排序,點擊三次是不能排序。默認地,是沒有排序。
用戶可以萬惡表格中的多個列進行排序,並在排序操作中指定各列的優先級。要排序多列,在點擊列標題的時候按住Shift鍵即可。
在Figure 13-7 中,第一列應用了升序,第二列是降序。注意第一列的優先級要高於第二列。
作爲開發者,可以通過 setSortType方法爲應用中的每一列設置排序參數。
可以指定是升序還是降序。比如,下面這行代碼設置了emailCol列是降序排序:emailCol.setSortType(TableColumn.SortType.DESCENDING);
可以通過從 TableView.sortOrder可見列表增加或刪除
TableColumn實例來指定要排序哪些列。該列表中列的順序就是排序的優先級
(比如,0項目的優先級高於第一個項目。)
使用setSortable(false)方法可以阻止列的排序。
編輯Table中的數據
TableView類不僅顯示錶格數據,也提供了編輯數據的功能。可以使用
TableView.edit(int row, TableColumn<S,?> column)
方法開始編輯。也可以使用TableCell類的方法編輯表格數據。見
Example 13-9 .
Example 13-9 Implementing Cell Editing
class EditingCell extends TableCell<Person, String> { private TextField textField; public EditingCell() { } @Override public void startEdit() { super.startEdit(); if (isEmpty()) { return; } if (textField == null) { createTextField(); } else { textField.setText(getItem()); } setGraphic(textField); setContentDisplay(ContentDisplay.GRAPHIC_ONLY); } @Override public void cancelEdit() { super.cancelEdit(); setContentDisplay(ContentDisplay.TEXT_ONLY); } @Override public void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (!isEmpty()) { if (textField != null) { textField.setText(item); } setText(item); } } private void createTextField() { textField = new TextField(getItem()); textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2); textField.setOnKeyReleased(new EventHandler<KeyEvent>() { @Override public void handle(KeyEvent t) { if (t.getCode() == KeyCode.ENTER) { commitEdit(textField.getText()); } else if (t.getCode() == KeyCode.ESCAPE) { cancelEdit(); } } }); } }
在Example 13-9 中,createTextField方法使用了
textField變量來分析輸入串並調用
commitEdit或
cancelEdit
方法(取決於按下了 Enter還是Escape鍵)
setCellFactory
方法能用來建立定製的細胞工廠。定製細胞工廠的首要任務是無論何時請求多返回一個新建的TableCell實例。
Example 13-10 展示瞭如何爲firstNameCol
, lastNameCol
和emailCol列實現細胞工廠。
Example 13-10 Using a Cell Factory
Callback<TableColumn, TableCell> cellFactory = new Callback<TableColumn, TableCell>() { public TableCell call(TableColumn p) { return new EditingCell(); } }; firstNameCol.setCellFactory(cellFactory); lastNameCol.setCellFactory(cellFactory); emailCol.setCellFactory(cellFactory);
用setOnEditCommit方法,如
Example 13-11 所示,這樣表格能處理項目的任何改變。該方法標識了一個編輯過的項目,取回了新數據,代替了data
可見列表的相應數據。
Example 13-11 Processing Edited Data in the Table
//Enabling editing table.setEditable(true); //Modifying the firstName property firstNameCol.setOnEditCommit(new EventHandler<CellEditEvent<Person, String>>() { @Override public void handle(CellEditEvent<Person, String> t) { ((Person)t.getTableView().getItems().get( t.getTablePosition().getRow())).setFirstName(t.getNewValue()); } }); //Modifying the lastName property lastNameCol.setOnEditCommit(new EventHandler<CellEditEvent<Person, String>>() { @Override public void handle(CellEditEvent<Person, String> t) { ((Person)t.getTableView().getItems().get( t.getTablePosition().getRow())).setLastName(t.getNewValue()); } }); //Modifying the email property emailCol.setOnEditCommit(new EventHandler<CellEditEvent<Person, String>>() { @Override public void handle(CellEditEvent<Person, String> t) { ((Person)t.getTableView().getItems().get( t.getTablePosition().getRow())).setEmail(t.getNewValue()); } });
在 Figure 13-8中,用戶編輯了Michael Brown的姓,輸入了新值並按了回車。回車後就不能再編輯了,這種行爲由TextField類的實現決定。
Figure 13-8 Editing a Table Cell