2014年10月18日 星期六

JavaFX Cell Factory (4)

JavaFX 8新增樹狀表格 (Tree Table),樹狀表格結合樹與表格物件,因此樹狀表格與後兩者十分類似,特色是將樹狀物件置於表格單元之中,讓表格單元中的資料以階層的方式呈現,下圖為樹狀表格的樣式:
其中:
  • Tree Table View:樹狀表格。
  • Tree Table Header:樹狀表格標題。
  • Tree Table Column:樹狀表格直行。
  • Tree Table Row:樹狀表格橫列。
  • Tree Table Cell:樹狀表格單元。
  • Tree Item:樹狀節點。
  • Table Menu Button:表格選單按鈕。
  • Column Sorter:表格直行排序器,可針對直行排序。
  • Editable Cell:可編輯的樹狀表格單元。
  • Vertical Line:垂直格線。
  • Scrollbar:捲軸,分為水平與垂直捲軸。
樹狀表格由Tree Table View、Tree Table Column、Tree Table Row與Tree Table Cell所組成,TreeTableView類別用以置放直行、橫列與樹狀表格單元,並顯示樹狀表格,為樹狀表格的最上層,如同TableViewTreeViewComboBoxListView類別一般,可修改各樹狀表格單元的形式,並分別依據直行或橫列設定其Cell Factory。

樹狀表格單元的類別為javafx.scene.control.TreeTableCell,繼承自TreeTableCell的類別包括:
  • 核取方塊樹狀表格單元:CheckBoxTreeTableCell.
  • 選項方塊樹狀表格單元:ChoiceBoxTreeTableCell.
  • 複合方塊樹狀表格單元:ComboBoxTreeTableCell.
  • 進度列樹狀表格單元:ProgressBarTreeTableCell.
  • 文字欄位樹狀表格單元:TextFieldTreeTableCell.
因此可使用核取方塊、選項方塊、複合方塊、進度列與文字欄位等取代原樹狀表格單元,較樹狀單元多了進度列物件。

以下範例以繼承TreeTableCell類別之自定TextFieldCellCallback類別做為setCellFactory()方法的回呼函式。在TextFieldCellCallback類別的建構函式中,以setAlignment()方法設定欄位內容為置中對齊:

private final class TextFieldCellCallback 
  extends TreeTableCell<Department, String> {

  TextField textfield;

  // 建構函式
  public TextFieldCellCallback() {
    // 設定欄位內容為置中對齊
    setAlignment(Pos.CENTER);
  }
  ...
}
...
TextFieldCellCallback類別中,分別覆寫TreeTableCell類別的startEdit()cancelEdit()updateItem()方法以處理開始編輯、取消編輯與更新樹狀表格單元等。

以開始編輯為例,在覆寫startEdit()方法中,當以滑鼠點選樹狀表格單元時,則以setGraphic()方法將樹狀表格單元設定為文字欄位,以此編輯樹狀表格單元的內容。此外,並以setOnKeyReleased()方法處理當在樹狀表格單元文字欄位釋放鍵盤按鍵時之事件,若按下Enter鍵,代表完成輸入,則以commitEdit()方法完成編輯樹狀表格單元;若按下ESC鍵,代表取消輸入,則以cancelEdit()方法取消編輯樹狀表格單元:

// 覆寫TreeTableCell類別的startEdit()方法
@Override public void startEdit() {
  super.startEdit();

  if (textfield == null) {
    textfield = new TextField(getItem() == null ? "" : getItem());
    textfield.setMinWidth(this.getWidth()-this.getGraphicTextGap()*2);
    
    textfield.setOnKeyReleased((KeyEvent e) -> {
      if (e.getCode() == KeyCode.ENTER) {
        // 完成編輯樹狀表格單元
        commitEdit(textfield.getText());
      } 
      else if (e.getCode() == KeyCode.ESCAPE) {
        // 取消編輯樹狀表格單元
        cancelEdit();
      }
    });
  }

  setText(null);
  // 將樹狀表格單元設定為文字欄位
  setGraphic(textfield);
  textfield.selectAll();
}
...
須注意的是,由於需要編輯樹狀表格單元,因此須以TreeTableView類別的setEditable()方法設定Tree Table View為可編輯狀態,並以TreeTableView類別的setCellFactory()方法以下列方式設定Cell Factory的回呼函式:
TreeTableView<Department> treeTableView = null;
treeTableView = new TreeTableView<>();
// 設定Tree Table View為可編輯狀態
treeTableView.setEditable(true);

// 建立直行
TreeTableColumn<Department, String> treeTableColumn1 = 
  new TreeTableColumn<>("Faculty");

// 設定Cell Factory的回呼函式
treeTableColumn1.setCellFactory(
  (TreeTableColumn<Department, String> value) -> 
  new TextFieldCellCallback());
...
【執行結果】
藉由繼承TreeTableCell類別之自定類別做為setCellFactory()方法的回呼函式,可使用各種物件取代原樹狀表格單元,以便編輯樹狀表格單元內容

此方式雖然可行,但必須覆寫TreeTableCell類別的startEdit()cancelEdit()commitEdit()updateItem()方法,因此有一定難度。 此外,JavaFX提供以下類別,可分別以核取方塊、選項方塊、複合方塊、進度列與文字欄位等取代原樹狀表格單元類別彼此之間所提供的方法十分類似,使用上亦很容易:
  • 核取方塊樹狀表格單元:CheckBoxTreeTableCell.
  • 選項方塊樹狀表格單元:ChoiceBoxTreeTableCell.
  • 複合方塊樹狀表格單元:ComboBoxTreeTableCell.
  • 進度列樹狀表格單元:ProgressBarTreeTableCell.
  • 文字欄位樹狀表格單元:TextFieldTreeTableCell.
CheckBoxTreeTableCell類別類似於CheckBoxTableCell類別,以建立核取方塊樣式的樹狀表格單元,功能與核取方塊十分類似,其選取狀態分為未勾選與已勾選。以下範例以TreeTableColumn類別的setCellFactory()方法設定Cell Factory為CheckBoxTreeTableCell,並以其forTreeTableColumn()方法以核取方塊取代原樹狀表格單元:
// 建立直行
TreeTableColumn<Department, Boolean> treeTableColumn5 = 
  new TreeTableColumn<>("Check");
// 設定直行最小寬度
treeTableColumn5.setPrefWidth(60);
// 設定直行標題的圖像
treeTableColumn5.setGraphic(new ImageView(new Image(
  getClass().getResourceAsStream("images/checkbox.gif"))));
// 設定Cell Factory
treeTableColumn5.setCellFactory(
  CheckBoxTreeTableCell.forTreeTableColumn(treeTableColumn5));
treeTableColumn5.setEditable(true);
...
【執行結果】
ChoiceBoxTreeTableCell類別類似於ChoiceBoxTableCell類別,以建立選項方塊樣式的樹狀表格單元,功能與選項方塊十分類似,當點選樹狀表格單元時,則以「下拉式選單」的方式列出選項項目,由於包含一個以上的選項項目,因此ChoiceBoxTreeTableCell類別必須以陣列定義一組選項項目,以做為「下拉式選單」的選項內容。以下範例以TreeTableColumn類別的setCellFactory()方法設定Cell Factory為ChoiceBoxTreeTableCell,並以其forTreeTableColumn()方法以選項方塊取代原樹狀表格單元,當點選樹狀表格單元時,則以「下拉式選單」的方式列出選項項目,其中以ObservableList設定選項方塊的選項項目:
// 建立直行
TreeTableColumn<Department, String> treeTableColumn5 = 
  new TreeTableColumn<>("Phone");
// 設定直行最小寬度
treeTableColumn5.setPrefWidth(60);
// 設定直行標題的圖像
treeTableColumn5.setGraphic(new ImageView(new Image(
  getClass().getResourceAsStream("images/choicebox.gif"))));

// 設定選項方塊的選項項目
ObservableList phonelists = FXCollections.observableArrayList(
  "4059", "4330", "5390",
  "5600", "5720", "6399",
  "6400", "6401", "6506");

// 設定Cell Factory
treeTableColumn5.setCellFactory(
  ChoiceBoxTreeTableCell.forTreeTableColumn(phonelists));
treeTableColumn5.setEditable(true);
...
【執行結果】
ComboBoxTreeTableCell類別類似於ComboBoxTableCell類別,以建立複合方塊樣式的樹狀表格單元,功能與複合方塊十分類似。以下範例以TreeTableColumn類別的setCellFactory()方法設定Cell Factory為ComboBoxTreeTableCell,並以其forTreeTableColumn()方法以複合方塊取代原樹狀表格單元,當點選樹狀表格單元時,則以「下拉式選單」的方式列出選項項目,其中以ObservableList設定複合方塊的選項項目:
// 建立直行
TreeTableColumn<Department, String> treeTableColumn5 = 
  new TreeTableColumn<>("Phone");
// 設定直行最小寬度
treeTableColumn5.setPrefWidth(60);
// 設定直行標題的圖像
treeTableColumn5.setGraphic(new ImageView(new Image(
  getClass().getResourceAsStream("images/combobox.gif"))));

// 設定選項方塊的選項項目
ObservableList phonelists = FXCollections.observableArrayList(
  "4059", "4330", "5390",
  "5600", "5720", "6399",
  "6400", "6401", "6506");

// 設定Cell Factory
treeTableColumn5.setCellFactory(
  ComboBoxTreeTableCell.forTreeTableColumn(phonelists));
treeTableColumn5.setEditable(true);
...
【執行結果】
TextFieldTreeTableCell類別類似於TextFieldTableCell類別,以建立文字欄位樣式的樹狀表格單元,當以滑鼠點選樹狀表格單元時,則將原樹狀表格單元轉換為文字欄位,以此編輯樹狀表格單元內容。以下範例以TreeTableColumn類別的setCellFactory()方法設定Cell Factory為TextFieldTreeTableCell,並以其forTreeTableColumn()方法以文字欄位取代原樹狀表格單元,當以滑鼠點選樹狀表格單元時,則將原樹狀表格單元轉換為文字欄位,以此編輯樹狀表格單元內容:
// 建立直行
TreeTableColumn<Department, String> treeTableColumn1 = 
  new TreeTableColumn<>("Faculty");
// 設定直行最小寬度
treeTableColumn1.setPrefWidth(80);
// 設定直行標題的圖像
treeTableColumn1.setGraphic(new ImageView(new Image(
  getClass().getResourceAsStream("images/faculty.gif"))));

// 設定直行對應於資料陣列的順序
treeTableColumn1.setCellValueFactory(
  (TreeTableColumn.CellDataFeatures<Department, String> param) ->
  new ReadOnlyStringWrapper(param.getValue().getValue().getFaculty())
);

// 設定Cell Factory
treeTableColumn1.setCellFactory(
  TextFieldTreeTableCell.forTreeTableColumn());
treeTableColumn1.setEditable(true);
...
【執行結果】
【參考資料】

[1] Java Official Web Site:http://www.oracle.com/technetwork/java/index.html
[2] JavaFX:http://www.oracle.com/technetwork/java/javafx
[3] JavaFX 8.0 API Specification.
[4] Java Platform, Standard Edition 8 API Specification.

© Chia-Hui Huang

6 則留言:

  1. 請教一下:
    Swing上的JLabel有HTML的功能,但JavaFX2好像沒有?
    小弟想實現換行的功能,上網查了一下,JavaFX8才有?

    回覆刪除
    回覆
    1. 確實JavaFX的Label類別只支援純文字,不支援HTML標籤。若為處理超連結,可使用javafx.scene.control.Hyperlink類別,請參考12-1節的說明。

      刪除
  2. 不好意思,路徑是這樣嗎?
    …getResourceAsStream("images/choicebox.gif"):./images/choicebox.gif(相對)
    …getResource("images/choicebox.gif"):src/images/choicebox.gif(絕對)
    css(-fx-background-image(images/choicebox.gif):./images/choicebox.gif(相對)

    回覆刪除
    回覆
    1. 使用getClass().getResourceAsStream("images/choicebox.gif")就可以了

      刪除
    2. 不好意思,小弟的意思是說:

      實體上的位置(src/Image/Img.png),因為小弟想放在jar檔的外面,直接改比較方面…

      刪除
  3. 哈…FXML太像HTML了,最近也在寫JSP…

    請教一下,小弟在這裡看到一個範例,移了一下位置,反而有一個會error:
    當然相對位置都有改到,小弟是想變成MVC,把檔案切開…

    http://d.hatena.ne.jp/aoe-tk/20130526/1369577773

    ----------------------------------------------------------------------

    例子(一個ok 一個error):

    https://drive.google.com/file/d/0B2z0BtOkkf7gcHZGR29Od21jVjg/view?usp=sharing
    https://drive.google.com/file/d/0B2z0BtOkkf7gdUpock5VMHhCdjA/view?usp=sharing

    -----------------------------------------------------

    十分期待您的JavaFX8的書,聽說…Andriod好像也要考慮加入JavaFX了…

    onAction="RUN" 跟 onAction="#RUN" 有分別嗎?

    回覆刪除