2014年10月16日 星期四

JavaFX Cell Factory (2)

JavaFX的樹狀物件 (Tree) 由Tree View、Tree Item與Tree Cell所組成,TreeView類別如同ComboBoxListView類別一般,將各節點視為單元,稱為樹狀單元 (Tree Cell),藉由TreeView類別的setCellFactory()方法設定Cell Factory,將每一個樹狀單元變更為其他物件,以改變節點的形式。

樹狀單元的類別為javafx.scene.control.TreeCell,繼承自TreeCell的類別包括:

  • 核取方塊樹狀單元:CheckBoxTreeCell.
  • 選項方塊樹狀單元:ChoiceBoxTreeCell.
  • 複合方塊樹狀單元:ComboBoxTreeCell.
  • 文字欄位樹狀單元:TextFieldTreeCell.
以下範例以繼承TreeCell類別之自定TreeCellCallback類別做為setCellFactory()方法的回呼函式。 在TreeCellCallback類別的建構函式中建立快顯選單,當在節點上按下滑鼠右鍵時,則顯示快顯選單以新增節點。此外,為控制快顯選單只能在非葉節點上執行,類別中覆寫TreeCell類別的updateItem()方法,分別以isLeaf()方法判斷節點是否為葉節點、getParent()方法判斷上一層是否無節點 (則為根節點),若為葉節點與根節點,則不執行快顯選單:
private final class TreeCellCallback extends TreeCell<String> {
  TextField textfield;
  ContextMenu contextmenu = new ContextMenu();

  // 建構函式
  public TreeCellCallback() {
    MenuItem menuitem = new MenuItem("Add Tree Node");
    menuitem.setGraphic(new ImageView(new Image(
      getClass().getResourceAsStream("images/node.gif"))));
    menuitem.setOnAction((ActionEvent e) -> {
      TreeItem treeitem = new TreeItem<>("New Tree Node...", 
        new ImageView(imgFile));
      getTreeItem().getChildren().add(treeitem);
    });

    contextmenu.getItems().add(menuitem);
  }

  @Override public void updateItem(String item, boolean empty) {
    super.updateItem(item, empty);

    if (empty) {
      setText(null);
      setGraphic(null);
    } 
    else {
      setText(getItem() == null ? "" : getItem());
      setGraphic(getTreeItem().getGraphic());

      if (!getTreeItem().isLeaf() && getTreeItem().getParent()!= null){
        setContextMenu(contextmenu);
      }
    }
  }
}
...
待完成自定類別之後,須以TreeView類別的setCellFactory()方法以下列方式設定Cell Factory的回呼函式:
treeView.setCellFactory(
  new Callback<TreeView<String>, TreeCell<String>>(){
    @Override public TreeCell<String> call(TreeView<String> value) {
      return new TreeCellCallback();
    }
  }
);
...
【執行結果】 
以下範例分別覆寫TreeCell類別的startEdit()cancelEdit()updateItem()方法以處理開始編輯、取消編輯與更新樹狀單元等。

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

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

  if (textfield == null) {
    textfield = new TextField(getItem() == null ? "" : getItem());
    
    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();
}
...
以取消編輯為例,在覆寫cancelEdit()方法中,由於取消編輯並未改變樹狀單元的內容,因此分別以setText()setGraphic()方法設定為原標題文字與圖像,以恢復節點為原來的內容:
// 覆寫TreeCell類別的cancelEdit()方法
@Override public void cancelEdit() {
  super.cancelEdit();

  // 設定為原標題文字
  setText((String) getItem());
  // 將節點設定為原圖像
  setGraphic(getTreeItem().getGraphic());
}
...
須注意的是,由於需要編輯節點,因此須以TreeView類別的setEditable()方法設定Tree View為可編輯狀態,並以TreeView類別的setCellFactory()方法以下列方式設定Cell Factory的回呼函式:
treeView.setEditable(true);

treeView.setCellFactory(
  new Callback<TreeView<String>, TreeCell<String>>(){
    @Override public TreeCell<String> call(TreeView<String> value) {
      return new TreeCellCallback();
    }
  }
);
...
【執行結果】 
藉由繼承TreeCell類別之自定類別做為setCellFactory()方法的回呼函式,可使用各種物件取代原樹狀節點,以便編輯節點內容

此方式雖然可行,但必須覆寫TreeCell類別的startEdit()cancelEdit()commitEdit()updateItem()方法,因此有一定難度。 此外,JavaFX提供以下類別,可分別以核取方塊、選項方塊、複合方塊與文字欄位等取代原樹狀單元,類別彼此之間所提供的方法十分類似,使用上亦很容易:
  • 核取方塊樹狀單元:CheckBoxTreeCell.
  • 選項方塊樹狀單元:ChoiceBoxTreeCell.
  • 複合方塊樹狀單元:ComboBoxTreeCell.
  • 文字欄位樹狀單元:TextFieldTreeCell.
CheckBoxTreeCell類別類似於CheckBox類別,以建立核取方塊樣式的樹狀節點。以下範例以TreeItem類別建立樹狀節點,並以TreeView類別的setCellFactory()方法設定Cell Factory為CheckBoxTreeCell,並以其forTreeView()方法設定以核取方塊取代原樹狀節點,相較於之前的範例,範例更為精簡:
// 建立TreeView物件  
TreeView treeView = new TreeView();
// 設定樹狀結構的根節點
treeView.setRoot(treeRoot);
// 設定複選模式
treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// 設定是否顯示根節點
treeView.setShowRoot(true);
// 設定是否可以編輯
treeView.setEditable(true);

// 設定Cell Factory
treeView.setCellFactory(CheckBoxTreeCell.<String>forTreeView());
...
【執行結果】 
ChoiceBoxTreeCell類別類似於ChoiceBox類別,以建立選項方塊樣式的樹狀節點。以下範例以TreeItem類別建立樹狀節點,並以TreeView類別的setCellFactory()方法設定Cell Factory為ChoiceBoxTreeCell,並以其forTreeView()方法設定以選項方塊取代原樹狀節點,當點選節點時,則以「下拉式選單」的方式列出選項項目,其中以ObservableList設定選項方塊的選項項目:
// 建立TreeView物件  
TreeView treeView = new TreeView();
// 設定樹狀結構的根節點
treeView.setRoot(treeRoot);
// 設定複選模式
treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// 設定是否顯示根節點
treeView.setShowRoot(true);
// 設定是否可以編輯
treeView.setEditable(true);

// 設定選項方塊的選項項目
ObservableList<String> items = FXCollections.observableArrayList(
  "Profile", "Course", "Publication", "Book", "Project");

// 設定Cell Factory
treeView.setCellFactory(ChoiceBoxTreeCell.forTreeView(items));
...
【執行結果】 
ComboBoxTreeCell類別類似於ComboBox類別,以建立複合方塊樣式的樹狀節點。以下範例以TreeItem類別建立樹狀節點,並以TreeView類別的setCellFactory()方法設定Cell Factory為ComboBoxTreeCell,並以其forTreeView()方法設定以複合方塊取代原樹狀節點,當點選節點時,則以「下拉式選單」的方式列出選項項目,其中以ObservableList設定複合方塊的選項項目:
// 建立TreeView物件  
TreeView treeView = new TreeView();
// 設定樹狀結構的根節點
treeView.setRoot(treeRoot);
// 設定複選模式
treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// 設定是否顯示根節點
treeView.setShowRoot(true);
// 設定是否可以編輯
treeView.setEditable(true);

// 設定複合方塊的選項項目
ObservableList<String> items = FXCollections.observableArrayList(
  "Profile", "Course", "Publication", "Book", "Project");

// 設定Cell Factory
treeView.setCellFactory(ComboBoxTreeCell.forTreeView(items));
...
【執行結果】 
TextFieldTreeCell類別類似於TextField類別,以建立文字欄位樣式的樹狀節點,當以滑鼠點選節點時,則將原節點轉換為文字欄位,以此編輯節點標題。以下範例以TreeItem類別建立樹狀節點,並以TreeView類別的setCellFactory()方法設定Cell Factory為TextFieldTreeCell,並以其forTreeView()方法設定以文字欄位取代原樹狀節點,當以滑鼠點選節點時,則將原節點轉換為文字欄位,以此編輯節點標題:
// 建立TreeView物件  
TreeView treeView = new TreeView();
// 設定樹狀結構的根節點
treeView.setRoot(treeRoot);
// 設定複選模式
treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// 設定是否顯示根節點
treeView.setShowRoot(true);
// 設定是否可以編輯
treeView.setEditable(true);

// 設定Cell Factory
treeView.setCellFactory(TextFieldTreeCell.forTreeView());
...
【執行結果】 
【參考資料】

[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

沒有留言:

張貼留言