iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
Software Development

玩轉ABCL Lisp!用奇怪偏門的方式學習JavaFX系列 第 24 篇

lesson 15 (S2): 另一條從JAVA來的路,改用 JAVA 建置 Hello CalcFX 畫面

  • 分享至 

  • xImage
  •  

TL;DR

在使用 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 執行。

從 Java 載入 CalcFX 的 FXML

同樣使用 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

實作 CalcFXController

建立 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 方法。本篇先讓事件流程簡單運作:

  1. 從 ActionEvent 取得被按下的按鈕。
  2. 從按鈕取得文字,例如 7 或 +。
  3. 用對話框顯示按鈕文字,確認事件已經成功傳遞。

本篇先不在 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 設定而拋出例外。

編譯 CalcFX

不同於系列開篇的 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: 文字

https://ithelp.ithome.com.tw/upload/images/20261007/20112470Ua2jzd9392.png

從 ABCL 換成 Java

到這裡,CalcFX 已經從 Scene Builder 教學中的 ABCL Controller 改成 Java Controller,但畫面仍然使用同一份 FXML 和 CSS。兩條路線的差別可以整理成:

ABCL 路線:FXML → ABCL 建立的動態 Controller → JavaFX 事件
Java 路線:FXML → Java CalcFXController    → JavaFX 事件

本篇從 ABCL 換成 Java,主要是替換 Controller 的實作語言,並沒有重新實作計算機邏輯。改用 Java 載入畫面的好處是:

  • JavaFX SDK、IDE 和 Java 編譯器可以直接配合使用。
  • Controller 可以直接在 FXML 指定。
  • 不需要先理解 ABCL 與 Java 互動的語法,就能編譯和修改 CalcFX。
  • 後續可以使用 jlink 和 jpackage 製作 runtime 及安裝包。

ABCL 適合互動式探索 Java 類別、快速測試 JavaFX API,以及動態改變程式結構;Java 則比較適合固定程式結構與打包分發。下一篇將從 Java 呼叫 ABCL,讓 JavaFX 畫面繼續使用 ABCL 實作的 CalcFX 計算邏輯。


上一篇
lesson 14: CalcFX with Controller Handler:使用畫面設計工具綁定元件變數與動作處理
下一篇
lesson 16 (S2): 透過 JAVA 使用 ABCL,實現 CalcFX 邏輯
系列文
玩轉ABCL Lisp!用奇怪偏門的方式學習JavaFX 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言