2014年10月24日 星期五

JavaFX 8技術手冊

今天完成JavaFX 8技術手冊的第一校,排的真不錯,約760頁,可惜是黑白印刷,無法呈現效果 (Effect) 與JavaFX 3D的色彩與美感。

期待新書出版,以下是我借用caterpillar林信良所著之Java SE 8 技術手冊所改編的封面,書商不見得會使用,但自得其樂。


© Chia-Hui Huang

JavaFX 3D (3) PhongMaterial貼圖處理

今天刊登於CodeData網站,有關JavaFX 3D的PhongMaterial貼圖處理:

http://www.codedata.com.tw/java/javafx-3d-3-phongmaterial/

© Chia-Hui Huang

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

2014年10月17日 星期五

JavaFX Cell Factory (3)

表格 (Table) 為視窗程式中重要的物件之一,常運用於試算表與資料庫,表格以直行 (Column) 與橫列 (Row) 的方式呈現資料,每一直行與橫列交會之處稱為表格單元 (Table Cell)。

表格物件由Table View、Table Column、Table Row與Table Cell所組成,TableView類別用以置放直行、橫列與表格單元,並顯示表格物件,為表格物件的最上層,如同TreeViewComboBoxListView類別一般,可修改各表格單元的形式,不同的是,JavaFX的表格分別依據直行或橫列設定其Cell Factory。

表格單元的類別為javafx.scene.control.TableCell,繼承自TableCell的類別包括:

  • 核取方塊表格單元:CheckBoxTableCell.
  • 選項方塊表格單元:ChoiceBoxTableCell.
  • 複合方塊表格單元:ComboBoxTableCell.
  • 進度列表格單元:ProgressBarTableCell.
  • 文字欄位表格單元:TextFieldTableCell.
因此可使用核取方塊、選項方塊、複合方塊、進度列與文字欄位等取代原表格單元,較樹狀單元多了進度列物件。

以下範例以繼承TableCell類別之自定TextFieldCellCallback類別做為setCellFactory()方法的回呼函式。在TextFieldCellCallback類別的建構函式中,以setAlignment()方法設定欄位內容為置中對齊,並建立快顯選單,當在表格上按下滑鼠右鍵時,將顯示快顯選單以新增一橫列:

private final class TextFieldCellCallback extends TableCell<> {
  TextField textfield;
  ContextMenu contextmenu = new ContextMenu();

  // 建構函式
  public TextFieldCellCallback() {
    // 設定欄位內容為置中對齊
    setAlignment(Pos.CENTER);
    // 建立快顯選單
    MenuItem menuitem = new MenuItem("Add New Row");
    menuitem.setGraphic(new ImageView(new Image(
      getClass().getResourceAsStream("images/row.gif"))));

    menuitem.setOnAction((ActionEvent e) -> {
      // 新增一表格橫列
      data.add(new Product("", "", "", "", ""));
    });
    contextmenu.getItems().add(menuitem);
  }
  ...
}
...
TextFieldCellCallback類別中,分別覆寫TableCell類別的startEdit()cancelEdit()updateItem()方法以處理開始編輯、取消編輯與更新表格單元等。

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

// 覆寫TableCell類別的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();
}
...
須注意的是,由於需要編輯表格單元,因此須以TableView類別的setEditable()方法設定Table View為可編輯狀態,並以TableView類別的setCellFactory()方法以下列方式設定Cell Factory的回呼函式:
TableView<Product> tableView = new TableView<>();
// 設定Table View為可編輯狀態
tableView.setEditable(true);

TableColumn column[] = new TableColumn[title.length];

column[i].setCellFactory(new Callback<TableColumn<Product, String>, 
  TableCell<Product, String>>() {
  @Override public TableCell<Product, String> call(
    TableColumn<Product, String> value) {
      return new TextFieldCellCallback();
  }
});
...
【執行結果】 
藉由繼承TableCell類別之自定類別做為setCellFactory()方法的回呼函式,可使用各種物件取代原表格單元,以便編輯表格單元內容

此方式雖然可行,但必須覆寫TableCell類別的startEdit()cancelEdit()commitEdit()updateItem()方法,因此有一定難度。 此外,JavaFX提供以下類別,可分別以核取方塊、選項方塊、複合方塊、進度列與文字欄位等取代原表格單元類別彼此之間所提供的方法十分類似,使用上亦很容易:
  • 核取方塊表格單元:CheckBoxTableCell.
  • 選項方塊表格單元:ChoiceBoxTableCell.
  • 複合方塊表格單元:ComboBoxTableCell.
  • 進度列表格單元:ProgressBarTableCell.
  • 文字欄位表格單元:TextFieldTableCell.
CheckBoxTableCell類別類似於CheckBox類別,以建立核取方塊樣式的表格單元,功能與核取方塊十分類似,其選取狀態分為未勾選與已勾選。以下範例以TableColumn類別的setCellFactory()方法設定Cell Factory為CheckBoxTableCell,並以其forTableColumn()方法以核取方塊取代原表格單元,相較於之前的範例,範例更為精簡:
TableColumn column[] = new TableColumn[title.length];

for (int i=0; i<title.length; i++) {
  column[i] = new TableColumn(title[i]);
  column[i].setMinWidth(width[i]);
  // 設定直行標題的圖像
  column[i].setGraphic(new ImageView(new Image(
    getClass().getResourceAsStream("images/icon" + (i+1) + ".gif"))));

  if (i!=4) {
    // 設定直行對應於資料陣列的順序
    column[i].setCellValueFactory(
      new PropertyValueFactory<>(cellValue[i]));
  }
  else { // CheckBoxTableCell
    // 設定直行對應於資料陣列的順序
    column[i].setCellValueFactory(
      new PropertyValueFactory<>(cellValue[i]));

    // 設定Cell Factory
    column[i].setCellFactory(
      CheckBoxTableCell.forTableColumn(column[i]));
    column[i].setEditable(true);
  }
}
...
【執行結果】 
ChoiceBoxTableCell類別類似於ChoiceBox類別,以建立選項方塊樣式的表格單元,功能與選項方塊十分類似,當點選表格單元時,則以「下拉式選單」的方式列出選項項目,由於包含一個以上的選項項目,因此ChoiceBoxTableCell類別必須以陣列定義一組選項項目,以做為「下拉式選單」的選項內容。以下範例以TableColumn類別的setCellFactory()方法設定Cell Factory為ChoiceBoxTableCell,並以其forTableColumn()方法以選項方塊取代原表格單元,當點選表格單元時,則以「下拉式選單」的方式列出選項項目,其中以ObservableList設定選項方塊的選項項目:
TableColumn column[] = new TableColumn[title.length];
...

// 設定選項方塊的選項項目
ObservableList<String> items = FXCollections.observableArrayList(
  "JX001", "JX002", "JX003", "JX004", "JX005", 
  "JX006", "JX007", "JX008", "JX009", "JX010");

column[i] = new TableColumn(title[i]);
// 設定直行對應於資料陣列的順序
column[i].setCellValueFactory(
  new PropertyValueFactory<>(cellValue[i]));
// 設定Cell Factory
column[i].setCellFactory(
  ChoiceBoxTableCell.forTableColumn(items));
column[i].setEditable(true);
...
【執行結果】 
ComboBoxTableCell類別類似於ComboBox類別,以建立複合方塊樣式的表格單元,功能與複合方塊十分類似。以下範例以TableColumn類別的setCellFactory()方法設定Cell Factory為ComboBoxTableCell,並以其forTableColumn()方法以複合方塊取代原表格單元,當點選表格單元時,則以「下拉式選單」的方式列出選項項目,其中以ObservableList設定複合方塊的選項項目:
TableColumn column[] = new TableColumn[title.length];
...

// 設定選項方塊的選項項目
ObservableList<String> items = FXCollections.observableArrayList(
  "JX001", "JX002", "JX003", "JX004", "JX005", 
  "JX006", "JX007", "JX008", "JX009", "JX010");

column[i] = new TableColumn(title[i]);
// 設定直行對應於資料陣列的順序
column[i].setCellValueFactory(
  new PropertyValueFactory<>(cellValue[i]));
// 設定Cell Factory
column[i].setCellFactory(
  ComboBoxTableCell.forTableColumn(items));
column[i].setEditable(true);
...
【執行結果】 
ProgressBarTableCell類別類似於ProgressBar類別,以建立進度列樣式的表格單元,顯示進度之用。為處理進度列顯示進度,範例以繼承Task抽象類別之自定ProgressBarTask類別做為處理進度之用:
private final class ProgressBarTask extends Task<Void> {
  int waiting; 
  int pausing;

  ProgressBarTask(int _waiting, int _pausing) {
    this.waiting = _waiting;
    this.pausing = _pausing;
  }

  @Override protected Void call() throws Exception {
    this.updateProgress(ProgressIndicator.INDETERMINATE_PROGRESS, 1);
    this.updateMessage("Waiting...");
    Thread.sleep(waiting);
    this.updateMessage("Running...");
    
    for (int i=0; i<500; i++) {
      updateProgress((1.0*i)/500, 1);
      Thread.sleep(pausing);
    }
    this.updateMessage("Done");
    this.updateProgress(1, 1);
    return null;
  }
}
...
接著以TableColumn類別的setCellFactory()方法設定Cell Factory為ProgressBarTableCell,並以其forTableColumn()方法以進度列取代原表格單元:
Random randomv = new Random();

for (int i=0; i<10; i++) {
  tableView.getItems().add(new ProgressBarTask(
    randomv.nextInt(5000)+1000, randomv.nextInt(50)+10));
}

TableColumn<ProgressBarTask, Double> column1 = 
  new TableColumn("Progress");
column1.setGraphic(new ImageView(new Image(
  getClass().getResourceAsStream("images/icon1.gif"))));
// 設定直行對應於資料陣列的順序
column1.setCellValueFactory(new PropertyValueFactory<>("progress"));
// 設定Cell Factory
column1.setCellFactory(
  ProgressBarTableCell.<ProgressBarTask>forTableColumn());

TableColumn<ProgressBarTask, String> column2 = 
  new TableColumn("Status");
column2.setGraphic(new ImageView(new Image(
  getClass().getResourceAsStream("images/icon2.gif"))));
// 設定直行對應於資料陣列的順序
column2.setCellValueFactory(new PropertyValueFactory<>("message"));
column2.setPrefWidth(80);
...
【執行結果】 
TextFieldTableCell類別類似於TextField類別,以建立文字欄位樣式的表格單元,當以滑鼠點選表格單元時,則將原表格單元轉換為文字欄位,以此編輯表格單元內容。以下範例以TableColumn類別的setCellFactory()方法設定Cell Factory為TextFieldTableCell,並以其forTableColumn()方法以文字欄位取代原表格單元,當以滑鼠點選表格單元時,則將原表格單元轉換為文字欄位,以此編輯表格單元內容:
TableColumn column[] = new TableColumn[title.length];
...

column[i] = new TableColumn(title[i]);
column[i].setMinWidth(width[i]);
// 設定直行對應於資料陣列的順序
column[i].setCellValueFactory(
  new PropertyValueFactory<>(cellValue[i]));
// 設定Cell Factory
column[i].setCellFactory(TextFieldTableCell.forTableColumn());
column[i].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

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

2014年10月15日 星期三

JavaFX Cell Factory (1)

Cell Factory是JavaFX一項特殊的功能,適用於Combo Box, List View, Table, Tree, Tree Table等物件。以Combo Box為例,Cell Factory將每一個選項項目視為單元,稱為清單單元 (List Cell),藉由設定Cell Factory將每一個選項項目變更為其他物件如文字字串、文字欄位或複合方塊等物件,以改變每一個選項項目的形式。

以下範例分別示範以文字字串與顏色選項修改Combo Box的選項項目,第一個複合方塊設定為文字字串之選項項目,以setCellFactory()方法設定其Cell Factory,並在Cell Factory中分別設定各選項項目的文字顏色:
String title[] = new String[10];

for (int i=0; i < 10; i++)    
  title[i] = "Item " + i;

final ComboBox combobox1 = new ComboBox();
    
combobox1.setItems(FXCollections.observableArrayList(title));
combobox1.setValue("Item 0");
combobox1.setPromptText("Editable");
combobox1.setVisibleRowCount(5);
combobox1.setEditable(true);

// Cell Factory
combobox1.setCellFactory(
  new Callback<ListView<String>, ListCell<String>>() {
  @Override  
  public ListCell<String> call(ListView<String> param) {
    
    return new ListCell<String>() 
    {
      {
        super.setPrefWidth(60);
      }    
      @Override 
      protected void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);

        if (item != null) {
          setText(item); 

          if (item.contains("Item 0")) 
            setTextFill(Color.RED);
          else if (item.contains("Item 1"))
            setTextFill(Color.GREEN);
          else if (item.contains("Item 2"))
            setTextFill(Color.BLUE);
          else if (item.contains("Item 3"))
            setTextFill(Color.AQUA);
          else if (item.contains("Item 4"))
            setTextFill(Color.BLUEVIOLET);
          else if (item.contains("Item 5"))
            setTextFill(Color.CHOCOLATE);
          else if (item.contains("Item 6"))
            setTextFill(Color.DIMGRAY);
          else if (item.contains("Item 7"))
            setTextFill(Color.FORESTGREEN);
          else if (item.contains("Item 8"))
            setTextFill(Color.GREENYELLOW);
          else if (item.contains("Item 9"))
            setTextFill(Color.INDIGO);
        }
        else {
          setText(null);
        }
      }
    };
  }
});
...
【執行結果】 
第二個複合方塊則設定為顏色之選項項目,以setCellFactory()方法設定其Cell Factory,並在Cell Factory中以Rectangle類別設定各選項項目的顏色:
final ComboBox<Color> combobox2 = new ComboBox<>();;
    
combobox2.getItems().addAll(
  Color.RED, Color.GREEN, Color.BLUE, Color.AQUA, 
  Color.BLUEVIOLET, Color.CHOCOLATE, Color.DIMGRAY, 
  Color.FORESTGREEN, Color.GREENYELLOW, Color.INDIGO);

// select the last element
combobox2.getSelectionModel().select(3);
combobox2.setVisibleRowCount(5);
combobox2.setEditable(false);

// Cell Factory
combobox2.setCellFactory(
  new Callback<ListView<Color>, ListCell<Color>>() {
  @Override  
  public ListCell<Color> call(ListView<Color> param) {
    
    return new ListCell<Color>() 
    {
      {
        super.setPrefWidth(60);
      }    
      private final Rectangle rectangle;
      { 
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY); 
        rectangle = new Rectangle(70, 15);
      }
         
      @Override  
      protected void updateItem(Color item, boolean empty) {
        super.updateItem(item, empty);
             
        if (item == null || empty) {
          setGraphic(null);
        } 
        else {
          rectangle.setFill(item);
          setGraphic(rectangle);
        }
      }
    };
  }
});
...
【執行結果】 
以List View為例,Cell Factory將每一個選項項目視為單元,稱為清單單元 (List Cell),藉由設定Cell Factory將每一個選項項目變更為其他物件如文字字串、文字欄位或複合方塊等物件,以改變每一個選項項目的形式。

以下範例分別示範以文字欄位與複合方塊修改List View的選項項目,當點選檢視清單的選項項目時,其清單單元分別轉換為文字欄位與複合方塊,因此選項項目可藉由文字欄位輸入資料或以複合方塊選擇選項項目。以下示範設定單選模式與設定文字欄位為檢視清單的Cell Factory:
ObservableList<String> data = FXCollections.observableArrayList(
  "January", "February", "March", "April", "May", "June", 
  "July", "August", "September", "October", "November", "December");

// ListView with TextFieldListCell
final ListView<String> listview1 = new ListView<>();
// 設定單選模式
listview1.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
listview1.setItems(data);
listview1.setPrefWidth(150);
listview1.setPrefHeight(100);
listview1.setOrientation(Orientation.VERTICAL);
listview1.setEditable(true);
listview1.setCellFactory(TextFieldListCell.forListView()); 
...

ObservableList<String> data = FXCollections.observableArrayList(
  "January", "February", "March", "April", "May", "June", 
  "July", "August", "September", "October", "November", "December");

// ListView with ComboBoxListCell
final ListView<String> listview2 = new ListView<>();
// 設定複選模式
Listview2.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
listview2.setItems(data);
listview2.setPrefWidth(150);
listview2.setPrefHeight(100);
listview2.setOrientation(Orientation.VERTICAL);
listview2.setEditable(true);
listview2.setCellFactory(ComboBoxListCell.forListView(data)); 
...
【執行結果】 
【參考資料】

[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

2014年10月10日 星期五

JavaFX 8 Tree Table

JavaFX 8新增樹狀表格(Tree Table),樹狀表格結合樹與表格物件,因此與後兩者十分類似,特色是將樹狀物件置於表格單元之中,讓表格單元中的資料以階層的方式呈現,下圖為樹狀表格的樣式:
JavaFX的樹狀表格物件由以下類別所組成:
  • javafx.scene.control.TreeTableView
  • javafx.scene.control.TreeTableColumn
  • javafx.scene.control.TreeTableRow
  • javafx.scene.control.TreeTableCell
  • javafx.scene.control.cell.CheckBoxTreeTableCell
  • javafx.scene.control.cell.ChoiceBoxTreeTableCell
  • javafx.scene.control.cell.ComboBoxTreeTableCell
  • javafx.scene.control.cell.TextFieldTreeTableCell
首先以TreeItem類別建立樹狀物件的根節點,並以setExpanded()方法設定節點的展開狀態:
// 建立根節點
TreeItem<String> treeRoot = new TreeItem<>("Root node");
// 設定節點的展開狀態
treeRoot.setExpanded(true);
...
接著以TreeItem類別建立子節點,並以TreeItem類別的getChildren().add()getChildren().addAll()方法依序將子節點加入至根節點之中:
// 建立子節點
TreeItem<String> treeItem1 = new TreeItem<>("Profile");
TreeItem<String> treeItem2 = new TreeItem<>("Course");
TreeItem<String> treeItem3 = new TreeItem<>("Publication");
TreeItem<String> treeItem4 = new TreeItem<>("Book");
TreeItem<String> treeItem5 = new TreeItem<>("Project");

// 依序將子節點加入至根節點之中
treeRoot.getChildren().addAll(
  treeItem1, treeItem2, treeItem3, treeItem4, treeItem5);
...
待建立樹狀物件之後,則以TreeTableColumn類別設定直行的相關屬性如直行標題,本範例僅設定一直行:
// 設定直行標題
TreeTableColumn<String,String> treeTableColumn = 
  new TreeTableColumn<>("Vita");

// 設定直行屬性
treeTableColumn.setCellValueFactory(
  (CellDataFeatures<String,String> p) -> 
  new ReadOnlyStringWrapper(p.getValue().getValue()));  
...
最後以TreeTableView類別的getColumns().add()getColumns().addAll()方法依序將直行加入至樹狀表格之中:
// 建立TreeTableView物件
TreeTableView<String> treeTableView = null;
treeTableView = new TreeTableView<>(treeRoot);

// 依序將直行加入至樹狀表格之中
treeTableView.getColumns().add(treeTableColumn);
// 設定是否顯示根節點
treeTableView.setShowRoot(true); 
// 設定是否可以選擇樹狀表格單元
treeTableView.getSelectionModel().setCellSelectionEnabled(false);
// 設定複選模式
treeTableView.getSelectionModel().setSelectionMode(
  SelectionMode.MULTIPLE);
// 設定直行的調整原則
treeTableView.setColumnResizePolicy(
  TreeTableView.CONSTRAINED_RESIZE_POLICY);
// 設定是否顯示樹狀表格選單按鈕
treeTableView.setTableMenuButtonVisible(true);
...
【執行結果】
除了上述範例的建立方式之外,亦可以陣列的方式處理多直行的樹狀表格單元。首先自定Department類別以建立節點陣列:
// 自定Department類別
public class Department {
  private SimpleStringProperty faculty;
  private SimpleStringProperty position;
  private SimpleStringProperty email;
  private SimpleStringProperty office; 
  private SimpleStringProperty phone;

  public Department(String _faculty, String _position, 
    String _email, String _office, String _phone) {

    this.faculty = new SimpleStringProperty(_faculty);
    this.position = new SimpleStringProperty(_position);
    this.email = new SimpleStringProperty(_email);
    this.office  = new SimpleStringProperty(_office);
    this.phone = new SimpleStringProperty(_phone);
  }

  public SimpleStringProperty facultyProperty() {
    if (faculty == null) {
      faculty = new SimpleStringProperty(this, "faculty");
    }
    return faculty;
  }
  ...

  public String getFaculty() {
    return faculty.get();
  }

  public void setFaculty(String _faculty) {
    faculty.set(_faculty);
  }
  ...
}  
...
以此自定的Department類別建立節點陣列:
// 建立節點陣列
ObservableList<Department> department = 
  FXCollections.observableArrayList(
    new Department("Athena", "Associate Professor", "athena@edu.tw",
      "725", "6400"),
    new Department("Leo", "Associate Professor", "leo@edu.tw",
      "813", "6506"),
    new Department("Teresa", "Professor", "teresa@edu.tw",
      "825", "4059"),
    ...);
...
【執行結果】
【參考資料】

[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

2014年10月6日 星期一

JavaFX 8 DatePicker

JavaFX 8新增日期選擇器 (Date Picker)。

日期選擇器的類別為javafx.scene.control.DatePicker,用以選擇日期,與ColorPicker類別同樣繼承自ComboBoxBase抽象類別,因此其樣式類似於複合方塊 (Combo Box)。

下圖為JavaFX日期選擇器的樣式,包括左側的編輯物件與右側的按鈕,當點選按鈕時,則顯示日期選擇器:
DatePicker類別定義以下的屬性值:
  • chronology:年曆系統。
  • converter:日期字串與日期格式間之型別轉換器。
  • dayCellFactory:日期選擇器的Cell Factory。
  • editor:日期選擇器的編輯物件。
  • showWeekNumbers:是否顯示週次。
其中年曆系統由java.time.chrono套件處理,chrono為Java S.E. 8新增的套件,內建支援以下年曆系統之類別:
  • HijrahChronology類別:伊斯蘭年曆系統。
  • IsoChronology類別:支援ISO-8601日期與時間表示方法之年曆系統。
  • JapaneseChronology類別:日本年曆系統,如明治、大正、昭和、平成等年曆。
  • MinguoChronology類別:中華民國年曆系統。
  • ThaiBuddhistChronology類別:泰國佛歷系統。
此外,DatePicker類別如同ComboBox類別一般支援Cell Factory,藉由設定Cell Factory將日期選擇器中的每一個日期變更為其他格式如改變顏色。 使用上相當簡單,當點選DatePicker時,將觸發動作事件 (Action Event),藉由getValue()方法則可取得所選擇的日期,例如:
final DatePicker datepicker = new DatePicker(); 

datepicker.setOnAction(event -> { 
  // 取得所選擇的日期 
  LocalDate date = datepicker.getValue(); 
  System.out.println("選擇的日期為: " + date); 
});
...
【執行結果】
以下範例加入選單以分別選擇年曆系統、設定今天日期與是否顯示週次。以選擇年曆系統為例,當選擇各年曆的選單項目時,則以setChronology()方法設定日期選擇器的年曆系統:
ToggleGroup togglegroup = new ToggleGroup();

RadioMenuItem radiomenuitem1 = new RadioMenuItem("Hijrah");
radiomenuitem1.setSelected(false);
radiomenuitem1.setToggleGroup(togglegroup);
radiomenuitem1.setOnAction((ActionEvent e) -> {
  // 設定伊斯蘭年曆系統
  datepicker.setChronology(HijrahChronology.INSTANCE);
});

RadioMenuItem radiomenuitem2 = new RadioMenuItem("ISO-8601");
radiomenuitem2.setSelected(true);
radiomenuitem2.setToggleGroup(togglegroup);
radiomenuitem2.setOnAction((ActionEvent e) -> {
  // 設定ISO-8601年曆系統
  datepicker.setChronology(IsoChronology.INSTANCE);
});

RadioMenuItem radiomenuitem3 = new RadioMenuItem("Japanese");
radiomenuitem3.setSelected(false);
radiomenuitem3.setToggleGroup(togglegroup);
radiomenuitem3.setOnAction((ActionEvent e) -> {
  // 設定日本年曆系統
  datepicker.setChronology(JapaneseChronology.INSTANCE);
});

RadioMenuItem radiomenuitem4 = new RadioMenuItem("R.O.C.");
radiomenuitem4.setSelected(false);
radiomenuitem4.setToggleGroup(togglegroup);
radiomenuitem4.setOnAction((ActionEvent e) -> {
  // 設定中華民國年曆系統
  datepicker.setChronology(MinguoChronology.INSTANCE);
});

RadioMenuItem radiomenuitem5 = new RadioMenuItem("Thai Buddhist");
radiomenuitem5.setSelected(false);
radiomenuitem5.setToggleGroup(togglegroup);
radiomenuitem5.setOnAction((ActionEvent e) -> {
  // 設定泰國佛歷年曆系統
  datepicker.setChronology(ThaiBuddhistChronology.INSTANCE);
});
...
【執行結果】

以下為選擇伊斯蘭年曆系統的執行結果:
以下為選擇中華民國年曆系統的執行結果:
此外,DatePicker類別如同ComboBox類別一般支援Cell Factory,藉由設定Cell Factory將日期選擇器中的每一個日期變更為其他內容與格式。以下範例加入設定日期格式與日期選擇器的Cell Factory。以設定日期格式為例,範例以DateTimeFormatter類別設定日期格式,並以DatePicker類別的setConverter()方法設定日期格式的型別轉換器:
// 設定日期格式
String pattern = "MMM-dd-yyyy";

StringConverter converter = new StringConverter<localdate>() {
  DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);

  @Override public String toString(LocalDate date) {
    if (date != null) {
      return formatter.format(date);
    } 
    else {
      return "";
    }
  }

  @Override public LocalDate fromString(String string) {
    if (string != null && !string.isEmpty()) {
      return LocalDate.parse(string, formatter);
    } 
    else {
      return null;
    }
  }
};             

datepicker = new DatePicker(); 
// 設定日期格式的型別轉換器
datepicker.setConverter(converter);
...
【執行結果】
欲設定日期選擇器的Cell Factory,則以setDayCellFactory() 方法設定日期選擇器的Cell Factory,並在Cell Factory中分別設定今日之前日期的背景顏色與今日之後日期的提示說明:
// 設定日期選擇器的Cell Factory
datepicker.setDayCellFactory(new Callback<DatePicker, DateCell>() {
  @Override public DateCell call(final DatePicker datePicker) {
    return new DateCell() {
      @Override public void updateItem(LocalDate item, boolean empty) {
        super.updateItem(item, empty);

        LocalDate today = LocalDate.now();
        LocalDate tomorrow = today.plusDays(1);

        if (item.isBefore(today)) {
          setDisable(true);
          // 設定背景顏色
          setStyle("-fx-background-color: #93d1f0;");
        }   
        else if (item.equals(today)) {
          // 設定提示說明
          setTooltip(new Tooltip("Today is " + today));
        }
        else if (item.equals(tomorrow)) {
          // 設定提示說明
          setTooltip(new Tooltip("Tomorrow is " + tomorrow));
        }
        // 設定提示說明
        else if (item.isAfter(tomorrow)) {
          setTooltip(new Tooltip("Day after " + tomorrow));
        }
      }
    };
  }
});
...
【執行結果】
【參考資料】

[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

JavaFX 8技術手冊

終於完成「JavaFX 8技術手冊」一書,包括以下章節,其中較特別的是樹狀表格 (Tree Table) 與JavaFX 3D,光JavaFX 3D一章就寫了近100頁,範例共49個,但還意猶未盡,JavaFX 3D真的比Java 3D與JOGL簡單精彩多了,特別是TriangleMesh類別:

1. JavaFX
2. JavaFX程式架構
3. Layout Pane
4. 效果
5. 事件
6. 按鈕
7. 選項項目
8. 捲軸、滑動軸、進度指示器與進度列
9. 選單
10. 文字編輯物件
11. 窗格
12. 對話盒
13. Web
14. 樹
15. 表格
16. 樹狀表格
17. 繪圖
18. 動畫
19. 圖表
20. 多媒體
21. 3D

這本完成之後就真的要封筆,暫時不再寫書了。

© Chia-Hui Huang