天天看點

(翻譯)第十九回 JavaFX2.0 表格框TableView

原文位址http://download.oracle.com/javafx/2.0/ui_controls/table-view.htm

JavaFX SDK API在的好幾個類都被設計來以表格形式呈現資料。在 JavaFX應用中建立表格的最重要類是

TableView

,

TableColumn

和TableCell

。可以通過實作資料模型或者應用一個細胞工廠來産生表格。

表格的類提供了内置的功能來在必要的時候進行資料排序和重置大小。

Figure 13-1 是一個典型的表格,用來呈現位址簿中的聯系人資訊。 Figure 13-1 Table Sample Description of "Figure 13-1 Table Sample"

建立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

類建立的。在

中,它被加入到了

VBox

布局容器中,然而,你可以直接把它加入應用場景中。

定義了三列來存儲位址簿中的以下資訊:某個聯系人的名和姓還有電郵位址。列是用

TableColumn

類建立的。

TableView

類的

getColumns

方法把前面建立的列加入到表格中。在應用中,可以用這個方法動态的添加和移除列。

編譯運作的效果如下

Figure 13-2

.

Figure 13-2 Table Without Data

Description of "Figure 13-2 Table Without Data"

可以通過調用

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

把這些代碼加入到

, 然後編譯運作,表格的呈現效果如

Figure 13-3

Figure 13-3 Table with Nested Columns

Description of "Figure 13-3 Table with Nested Columns"

盡管表格被加入到了應用中,标準标題依然顯示的是"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 Table Populated with Data

Description of "Figure 13-4 Table Populated with Data"

新增行

中的表格包含了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的資訊現在在表格中顯示了。

Figure 13-6 Newly Added Entry

Description of "Figure 13-6 Newly Added Entry"

資料排序

TableView

類提供了内置的資料排序能力。。使用者可以點選列标題來改變資料順序。點選一次是遞增排序,點選兩次是遞減排序,點選三次是不能排序。預設地,是沒有排序。

使用者可以萬惡表格中的多個列進行排序,并在排序操作中指定各列的優先級。要排序多列,在點選列标題的時候按住Shift鍵即可。

Figure 13-7

中,第一列應用了升序,第二列是降序。注意第一列的優先級要高于第二列。

Figure 13-7 Sorting Multiple Columns

Description of "Figure 13-7 Sorting Multiple Columns"

作為開發者,可以通過

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

,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

Description of "Figure 13-8 Editing a Table Cell"