iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
Software Development

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

lesson 14: CalcFX with Controller Handler:使用畫面設計工具綁定元件變數與動作處理

  • 分享至 

  • xImage
  •  

TL;DR

這篇延續前一篇 Scene Builder 文章,說明如何在 FXML 中設定 Controller,讓 fx:id 自動注入元件,並透過 onAction 與 setController() 將按鈕事件交給 ABCL 建立的動態 Java 類別處理。最終會把前一篇透過 lookup() 和遍歷子元件的做法,改成由 Controller 統一管理。

設定 FXML Controller

可以在 FXML 的根元件設定 Controller,透過 fx:controller 屬性指定:

<GridPane fx:controller="YourController"></GridPane>

在 Scene Builder 中,也可以從左下方的 Controller 設定區指定:

https://ithelp.ithome.com.tw/upload/images/20261006/20112470BWKTDkDtKV.png

設定 Controller 的好處是,FXML 載入時會直接與 Controller 類別關聯,並將具有 fx:id 的元件注入 Controller 實例。例如,指定顯示器的 fx:id="display":

      <TextField fx:id="display"  />

在 Scene Builder 中選取顯示器元件,再從右側的 Code 區域設定這個屬性。

不過目前是從 ABCL 使用 JavaFX,Controller 是在 ABCL 中建立的動態 Java 類別,不能直接在 FXML 中指定一個固定的 Controller 類別名稱。需要在 ABCL 程式碼中建立這個動態 Controller 類別,再由 new 建立類別實例,傳給 setController(controller):

(defun prepare-parent nil
  (let* ((loader (new "javafx.fxml.FXMLLoader"  (new "java.net.URL" (get-system-resource "res/CalcFX.fxml")))))
    (#"setController" loader (new *calculator-controller-class*))
    (#"load" loader)))

FXMLLoader 載入 FXML 後,會將 fx:id="display" 對應的 TextField 注入 Controller 中同名的 display 欄位:

(define-symbol-macro *calculator-controller-class*
  (java:jnew-runtime-class
   "CalculatorController"
   ;; 定義欄位:讓 JavaFX 能夠將 fx:id="display" 的元件注入進來
   :fields '(("display" "javafx.scene.control.TextField"))))

綁定按鈕動作

除了注入元件之外,還可以綁定動作事件。將所有計算機按鈕的按下動作,綁定到 *calculator-controller-class* 的 handleButtonClick 方法:

<Button onAction="#handleButtonClick" text="7" GridPane.rowIndex="1" styleClass="calc-btn" />
<Button onAction="#handleButtonClick" text="8" GridPane.columnIndex="1" GridPane.rowIndex="1" styleClass="calc-btn" />
<Button onAction="#handleButtonClick" text="9" GridPane.columnIndex="2" GridPane.rowIndex="1" styleClass="calc-btn" />
<Button onAction="#handleButtonClick" text="4" GridPane.rowIndex="2" styleClass="calc-btn" />
<Button onAction="#handleButtonClick" text="5" GridPane.columnIndex="1" GridPane.rowIndex="2" styleClass="calc-btn" />
<Button onAction="#handleButtonClick" text="6" GridPane.columnIndex="2" GridPane.rowIndex="2" styleClass="calc-btn" />
<Button onAction="#handleButtonClick" text="1" GridPane.rowIndex="3" styleClass="calc-btn" />
<Button onAction="#handleButtonClick" text="2" GridPane.columnIndex="1" GridPane.rowIndex="3" styleClass="calc-btn" />
<Button onAction="#handleButtonClick" text="3" GridPane.columnIndex="2" GridPane.rowIndex="3" styleClass="calc-btn" />
<Button onAction="#handleButtonClick" text="C" GridPane.rowIndex="4" styleClass="calc-btn" />
<Button onAction="#handleButtonClick" text="0" GridPane.columnIndex="1" GridPane.rowIndex="4" styleClass="calc-btn" />
<Button onAction="#handleButtonClick" text="=" GridPane.columnIndex="2" GridPane.rowIndex="4"></Button>
<Button onAction="#handleButtonClick" text="/" GridPane.columnIndex="3" GridPane.rowIndex="1"></Button>
<Button onAction="#handleButtonClick" text="*" GridPane.columnIndex="3" GridPane.rowIndex="2"></Button>
<Button onAction="#handleButtonClick" text="-" GridPane.columnIndex="3" GridPane.rowIndex="3"></Button>
<Button onAction="#handleButtonClick" text="+" GridPane.columnIndex="3" GridPane.rowIndex="4"></Button>

設定元件事件處理的方式不只一種,這裡示範使用 Controller 方法的做法。# 前綴表示要綁定 Controller 中的方法。

https://ithelp.ithome.com.tw/upload/images/20261006/20112470CcNGffUTFd.png

如此一來,就不需要再透過 lookup() 找到顯示器,也不需要遍歷子元件後逐一綁定按鈕事件:

+ (defun prepare-parent nil
+   (let* ((loader (new "javafx.fxml.FXMLLoader"  (new "java.net.URL" (get-system-resource "res/CalcFX.fxml")))))
+     (#"setController" loader (new *calculator-controller-class*))
+     (#"load" loader)))


(defun create-calculator-scene ()
  "建立並回傳一個包含彈性計算機介面與事件綁定的 JavaFX Scene 物件"
+   (let* ((parent (prepare-parent))
+          (scene (new "javafx.scene.Scene" parent 320.0d0 420.0d0)))
-         (scene (new "javafx.scene.Scene" parent 320.0d0 420.0d0))
-         (display (#"lookup" scene "#display")))
-     (let* ((nodes (jcall "getChildren" parent))
-            (itr (jcall "iterator" nodes))
-            (listener (make-event-handler
-                       (event) (let* ((target (#"getTarget" event))
-                                      (label (#"getText" target)))
-                                 (handle-button-press label display)))))
-       (loop while (#"hasNext" itr)
-             do (let ((elm (#"next" itr)))
-                  (when (jtypep elm 'javafx.scene.control.Button)
-                   (#"setOnAction" elm listener)))))
    scene))

在動態 Controller 中實作事件方法

接著直接在 *calculator-controller-class* 中建立 handleButtonClick 方法:

(define-symbol-macro *calculator-controller-class*
  (java:jnew-runtime-class   "CalculatorController"
   ;; 定義欄位:讓 JavaFX 能夠將 fx:id="display" 的元件注入進來
   :fields '(("display" "javafx.scene.control.TextField"))

    ;; 定義方法:對應 Scene Builder 的 `onAction` 設定
   :methods `(("handleButtonClick" :void ("javafx.event.ActionEvent")
               (lambda (this event)
                 (let* ((target (#"getTarget" event))
                        (label (#"getText" target))
                        (display (jfield "display" this)))
                   (handle-button-press label display)))))))

將元件注入與事件處理放進 Controller *calculator-controller-class* 後,建立 CalcFX Scene 的函式就變得非常簡單。以下是載入 FXML 的完整程式碼:

(defun prepare-parent nil
  (let* ((loader (new "javafx.fxml.FXMLLoader"  (new "java.net.URL" (get-system-resource "res/CalcFX.fxml")))))
    (#"setController" loader (new *calculator-controller-class*))
    (#"load" loader)))

(defun create-calculator-scene ()
  "建立並回傳一個包含彈性計算機介面與事件綁定的 JavaFX Scene 物件"
  (let* ((parent (prepare-parent))
         (scene (new "javafx.scene.Scene" parent 320.0d0 420.0d0)))
    scene))

執行結果

現在的 CalcFX 使用 Scene Builder 建立 FXML 畫面,再由 ABCL 設定 Controller,完成顯示器元件注入與按鈕動作綁定。按下按鈕後,事件會交給 handleButtonClick,再呼叫前一篇建立的 handle-button-press 完成計算機邏輯。

至此,CalcFX 已經完成從 ABCL 建立畫面、改用 FXML 描述畫面,到使用 Scene Builder 設定元件與事件的學習路線。下一篇將回到 Java,重新使用 JavaFX 建立 CalcFX 應用畫面。


上一篇
lesson 13: CalcFX with SceneBuilder:使用畫面設計工具建立計算機畫面
下一篇
lesson 15 (S2): 另一條從JAVA來的路,改用 JAVA 建置 Hello CalcFX 畫面
系列文
玩轉ABCL Lisp!用奇怪偏門的方式學習JavaFX 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言