這篇先使用 ABCL 與 JavaFX 建立 CalcFX 的計算機畫面,完成顯示器、按鈕排列與 CSS 風格設定。這裡只處理 GUI 畫面,按鈕的計算功能會在下一篇實作。
計算機程式使用一個 4 x 5 的 Grid 布局,分成兩個部分:顯示器和按鈕區塊。Grid 每欄平分視窗寬度,所以每欄 25%,4 欄加總剛好 100%;第 0 列放置顯示器,不隨畫面變大;剩餘列放置按鈕,平均分配高度,每列 20% 的高度比例(以 0 開始計數)。
布局:
(grid (new "javafx.scene.layout.GridPane"))
(chain (#"getStyleClass" grid) ("add" "root-pane"))
(#"setVgap" grid 10.0d0) ; 垂直間距 10.0 (Java double)
(#"setHgap" grid 10.0d0) ; 水平間距 10.0 (Java double)
;; 共有 4 欄按鈕,每欄平均分配 25% 的寬度
(dotimes (i 4)
(let ((col (new "javafx.scene.layout.ColumnConstraints")))
(#"setPercentWidth" col 25.0d0)
(#"add" (#"getColumnConstraints" grid) col)))
;; 第 0 列(顯示幕)不自動隨視窗長高,維持原高度
(let ((row0 (new "javafx.scene.layout.RowConstraints")))
(#"setVgrow" row0 (jfield "javafx.scene.layout.Priority" "NEVER"))
(#"add" (#"getRowConstraints" grid) row0))
;; 第 1 到 4 列(按鈕列)要平均隨視窗長高 (百分比填滿)
(dotimes (i 4)
(let ((row (new "javafx.scene.layout.RowConstraints")))
(#"setPercentHeight" row 20.0d0) ; 每列分配總高度的 20%
(#"add" (#"getRowConstraints" grid) row)))
顯示器是一個不可編輯的 TextField,占滿整個 Grid 的第 0 列。
顯示器:
(display (new "javafx.scene.control.TextField" "0"))
(chain (#"getStyleClass" display) ("add" "display"))
;; 設定顯示幕樣式與唯讀,並讓它寬度無限拉伸
(#"setEditable" display nil)
(#"setMaxWidth" display (jfield "java.lang.Double" "MAX_VALUE"))
;; 將顯示幕加入 GridPane (欄0, 列0, 跨4欄, 跨1列)
(#"add" grid display 0 0 4 1)
按鈕依照表格布局放到 Grid 對應的剩餘位置:
| 7 | 8 | 9 | / |
| 4 | 5 | 6 | * |
| 1 | 2 | 3 | - |
| C | 0 | = | + |
按鈕又區分成數字按鈕和功能按鈕,目前兩者主要透過不同的 CSS 類別呈現風格差異。
按鈕:
;; 定義按鈕配置 (文字 . (欄 . 列))
(let ((buttons '(("7" . (0 . 1)) ("8" . (1 . 1)) ("9" . (2 . 1)) ("/" . (3 . 1))
("4" . (0 . 2)) ("5" . (1 . 2)) ("6" . (2 . 2)) ("*" . (3 . 2))
("1" . (0 . 3)) ("2" . (1 . 3)) ("3" . (2 . 3)) ("-" . (3 . 3))
("C" . (0 . 4)) ("0" . (1 . 4)) ("=" . (2 . 4)) ("+" . (3 . 4)))))
;; 迴圈建立按鈕
(dolist (btn-spec buttons)
(let* ((label (car btn-spec))
(col (cadr btn-spec))
(row (cddr btn-spec))
(btn (new "javafx.scene.control.Button" label))
(btn-styles (#"getStyleClass" btn)))
;; 重要:移除 setPrefSize 固定值,改讓它大小無限拉伸以符合格子約束
(#"setMaxSize" btn (jfield "java.lang.Double" "MAX_VALUE") (jfield "java.lang.Double" "MAX_VALUE"))
;; 根據功能套用不同 CSS 類別
(#"add" btn-styles "calc-btn")
(when (member label '("/" "*" "-" "+" "=") :test #'string=)
(#"add" btn-styles "op-btn"))
;; 將按鈕加入面板
(#"add" grid btn col row))))
設定使用的 CSS 風格檔案:
(let* ((scene (new "javafx.scene.Scene" grid 320.0d0 420.0d0))
(stylesheets (#"getStylesheets" scene))
(css-data-url (get-system-resource "res/calcfx.css")))
(#"add" stylesheets css-data-url)
scene)))
最後是 CSS 內容:
.root-pane {
-fx-background-color: #202020;
-fx-padding: 15;
}
.display {
-fx-background-color: #2d2d2d;
-fx-text-fill: white;
-fx-font-size: 28px;
-fx-alignment: center-right;
-fx-padding: 10;
-fx-background-radius: 5;
}
.calc-btn {
-fx-alignment: center;
-fx-font-size: 20px;
-fx-background-color: #3b3b3b;
-fx-text-fill: white;
-fx-background-radius: 5;
}
.calc-btn:hover { -fx-background-color: #4a4a4a; }
.op-btn {
-fx-background-color: #f1a33c;
-fx-text-fill: black;
}
.op-btn:hover { -fx-background-color: #f5b866; }
程式畫面與完整程式碼:

(defun create-calculator-scene ()
(let* ((grid (new "javafx.scene.layout.GridPane"))
(display (new "javafx.scene.control.TextField" "0"))) ;; 申明grid、display變數
(chain (#"getStyleClass" grid) ("add" "root-pane"))
(chain (#"getStyleClass" display) ("add" "display"))
(#"setVgap" grid 10.0d0) ; 垂直間距 10.0 (Java double)
(#"setHgap" grid 10.0d0) ; 水平間距 10.0 (Java double)
(#"setEditable" display nil)
(#"setMaxWidth" display (jfield "java.lang.Double" "MAX_VALUE"))
;; 共有 4 欄按鈕,每欄平均分配 25% 的寬度
(dotimes (i 4)
(let ((col (new "javafx.scene.layout.ColumnConstraints")))
(#"setPercentWidth" col 25.0d0)
(#"add" (#"getColumnConstraints" grid) col)))
;; 第 0 列(顯示幕)不自動隨視窗長高,維持原高度
(let ((row0 (new "javafx.scene.layout.RowConstraints")))
(#"setVgrow" row0 (jfield "javafx.scene.layout.Priority" "NEVER"))
(#"add" (#"getRowConstraints" grid) row0))
;; 第 1 到 4 列(按鈕列)要平均隨視窗長高 (百分比填滿)
(dotimes (i 4)
(let ((row (new "javafx.scene.layout.RowConstraints")))
(#"setPercentHeight" row 20.0d0) ; 每列分配總高度的 20%
(#"add" (#"getRowConstraints" grid) row)))
;; 定義按鈕配置 (文字 . (欄 . 列))
(#"add" grid display 0 0 4 1)
(let ((buttons '(("7" . (0 . 1)) ("8" . (1 . 1)) ("9" . (2 . 1)) ("/" . (3 . 1))
("4" . (0 . 2)) ("5" . (1 . 2)) ("6" . (2 . 2)) ("*" . (3 . 2))
("1" . (0 . 3)) ("2" . (1 . 3)) ("3" . (2 . 3)) ("-" . (3 . 3))
("C" . (0 . 4)) ("0" . (1 . 4)) ("=" . (2 . 4)) ("+" . (3 . 4)))))
;; 迴圈建立按鈕
(dolist (btn-spec buttons)
(let* ((label (car btn-spec))
(col (cadr btn-spec))
(row (cddr btn-spec))
(btn (new "javafx.scene.control.Button" label))
(btn-styles (#"getStyleClass" btn)))
;; 重要:移除 setPrefSize 固定值,改讓它大小無限拉伸以符合格子約束
(#"setMaxSize" btn (jfield "java.lang.Double" "MAX_VALUE") (jfield "java.lang.Double" "MAX_VALUE"))
;; 根據功能套用不同 CSS 類別
(#"add" btn-styles "calc-btn")
(when (member label '("/" "*" "-" "+" "=") :test #'string=)
(#"add" btn-styles "op-btn"))
;; 將按鈕加入面板
(#"add" grid btn col row))))
(let* ((scene (new "javafx.scene.Scene" grid 320.0d0 420.0d0))
(stylesheets (#"getStylesheets" scene))
(css-data-url (get-system-resource "res/calcfx.css")))
(#"add" stylesheets css-data-url)
scene)))
目前的 CalcFX 已完成計算機的畫面設計:顯示器位於上方,數字按鈕與運算按鈕依照表格排列,並透過 CSS 區分不同類型的按鈕。
下一篇將在這個畫面上加入計算機的功能邏輯,讓按鈕真正能夠處理輸入、運算與結果顯示。