在使用 Scene Builder 建立 CalcFX 畫面與動作處理中,已經使用 Scene Builder 建立 CalcFX 的 FXML,並由 ABCL 建立動態 Controller,處理 fx:id 和 onAction。
這一篇不重新設計畫面,而是沿用前一篇的 CalcFX.fxml,把啟動程式和 Controller 改寫成 Java。也就是說,繼續使用同一份 FXML 畫面,改由 Java 定義 CalcFXController,再交給 JavaFX 載入。
雖然從 ABCL 使用 JavaFX 很方便,但從 Java 實現 CalcFX 也有另一個好處:它讓 Controller、FXML 和編譯流程更貼近一般 Java 專案的寫法。接著我們就來看看,如何把 CalcFX 的 ABCL 版本改成 Java 執行。
同樣使用 FXMLLoader 載入 /res/CalcFX.fxml,再透過 setController() 將 CalcFXController 的實例指定給 FXMLLoader。改用 Java 後,啟動流程大致不變;不同的是,Controller 不再由 ABCL 動態建立,而是由 Java 類別實作。
package CalcFX;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.net.URL;
public class CalcFX extends Application {
@Override
public void start(Stage stage) throws Exception {
URL fxml = getClass().getResource("/res/CalcFX.fxml");
FXMLLoader loader = new FXMLLoader(fxml);
CalcFXController controller = new CalcFXController();
loader.setController(controller);
Parent parent = loader.load();
Scene scene = new Scene(parent, 320, 420);
stage.setTitle("CalcFX");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
對應 ABCL 版本來看,設定方式並沒有太大改變,主要差異是 Controller 不再由 ABCL 動態建立。ABCL 版本使用 new *calculator-controller-class* 建立動態類別的實例;Java 版本則使用 new CalcFXController() 建立 Java 類別的實例:
- (#"setController" loader (new *calculator-controller-class*))
+ CalcFXController controller = new CalcFXController();
+ loader.setController(controller);
此外,Java 可以直接使用 Class.getResource() 取得資源 URL;ABCL 版本則使用 #'get-system-resource 定位 ASDF 系統資源:
- (let* ((loader (new "javafx.fxml.FXMLLoader"
- (new "java.net.URL"
- (get-system-resource "res/CalcFX.fxml")))))
- (#"setController" loader (new *calculator-controller-class*))
+ URL fxml = getClass().getResource("/res/CalcFX.fxml");
+ FXMLLoader loader = new FXMLLoader(fxml);
另外要注意的是,Java 版本會明確區分編譯階段和執行階段。FXML 和 CSS 等資源也需要複製到執行階段的 class/ 目錄,讓它們能從 classpath 找到:
class/
├── CalcFX/
│ ├── CalcFX.class
│ └── CalcFXController.class
└── res/
├── CalcFX.fxml
└── calc.css
建立 CalcFX/CalcFXController.java:
package CalcFX;
import javafx.event.ActionEvent;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.TextField;
public class CalcFXController {
public TextField display;
public void handleButtonClick(ActionEvent event) {
Button target = (Button) event.getTarget();
String label = target.getText();
Alert dialog = new Alert(
Alert.AlertType.INFORMATION,
"clicked: " + label,
ButtonType.OK);
dialog.showAndWait();
}
}
同樣地,FXML 載入器會將顯示器元件注入 CalcFXController 的 display 欄位,並將按鈕的事件綁定到 handleButtonClick 方法。本篇先讓事件流程簡單運作:
ActionEvent 取得被按下的按鈕。7 或 +。本篇先不在 Java Controller 中重寫加減乘除邏輯,而是確認畫面能載入,且按鈕事件可以從 FXML 傳到 Java Controller。後續會再從 Java 呼叫 ABCL,沿用已經完成的計算機邏輯。
fx:controller前一篇沒有使用 fx:controller,是因為 Controller 是由 ABCL 建立的動態類別。這次改用一般 Java 類別,因此可以直接在 FXML 根元素設定 fx:controller:
<GridPane fx:controller="CalcFX.CalcFXController"
xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml/1">
<!-- 其餘欄列、顯示器和按鈕沿用前一篇 Scene Builder 教學的內容 -->
</GridPane>
如此可以將JAVA程式碼中的setController()移除掉:
URL fxml = getClass().getResource("/res/CalcFX.fxml");
FXMLLoader loader = new FXMLLoader(fxml);
- CalcFXController controller = new CalcFXController();
- loader.setController(controller);
- Parent parent = loader.load();
FXMLLoader 會依照完整類別名稱載入 Controller 類別,並建立 Controller 實例。
不過兩種方式只能選一種。如果 FXML 已經設定 fx:controller,就不能再呼叫 setController(),否則 FXMLLoader 會收到兩個 Controller 設定而拋出例外。
不同於系列開篇的 HelloFX 只有一個 Java 類別,這次有 CalcFX 和 CalcFXController 兩個類別。因此需要建立資料夾存放 Java 原始碼,以及對應的 Java package。總的來說,專案目錄如下:
CalcFX/
├── src/
│ └── CalcFX/
│ ├── CalcFX.java
│ └── CalcFXController.java
└── res/
├── CalcFX.fxml
└── calc.css
注意:
CalcFX並不是常見的 Java package 命名方式,這裡只是示例。
先編譯 Java 原始碼:
mkdir -p class
javac \
--module-path "${SDK_PATH}" \
--add-modules javafx.controls,javafx.fxml \
-d class \
src/CalcFX/*.java
Java 編譯器不會自動複製 FXML 和 CSS,因此還要把資源放到輸出目錄:
cp -r res class/
java \
--module-path "${SDK_PATH}" \
--add-modules javafx.controls,javafx.fxml \
-cp class \
CalcFX.CalcFX
執行後會看到用 Scene Builder 建立的 CalcFX 畫面。按下按鈕時,事件會依照這條路徑傳遞:
FXML 的 Button
↓
onAction="#handleButtonClick"
↓
CalcFXController.handleButtonClick()
↓
取得 event 的 target
↓
取得 Button 的文字
↓
顯示 clicked: 文字

到這裡,CalcFX 已經從 Scene Builder 教學中的 ABCL Controller 改成 Java Controller,但畫面仍然使用同一份 FXML 和 CSS。兩條路線的差別可以整理成:
ABCL 路線:FXML → ABCL 建立的動態 Controller → JavaFX 事件
Java 路線:FXML → Java CalcFXController → JavaFX 事件
本篇從 ABCL 換成 Java,主要是替換 Controller 的實作語言,並沒有重新實作計算機邏輯。改用 Java 載入畫面的好處是:
jlink 和 jpackage 製作 runtime 及安裝包。ABCL 適合互動式探索 Java 類別、快速測試 JavaFX API,以及動態改變程式結構;Java 則比較適合固定程式結構與打包分發。下一篇將從 Java 呼叫 ABCL,讓 JavaFX 畫面繼續使用 ABCL 實作的 CalcFX 計算邏輯。