iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Software Development

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

lesson 5: 怎麼存取靜態資源?Hello Icon & Resource裝飾視窗

  • 分享至 

  • xImage
  •  

TL;DR

這篇的重點是把 JavaFX 的 CSS 與圖示資源從「執行目錄」抽離出來,改成依賴 ASDF 系統位置。這樣不論從哪個目錄啟動,style.css 和 icon32x32.png 都能穩定被讀取,且畫面與圖示不會因為路徑變動而失效。

透過 Scene Stylesheet 載入資源

除了Application.setUserAgentStylesheet()外,還可以透過Scene.getStylesheets().add()設定場景的風格。調整scenes/java-info.lisp的檔案內容

(defun create-java-info-scene ()
  (prog* (......)
     (#"setAlignment" box (jfield "javafx.geometry.Pos" "CENTER"))
     (#"add" (#"getStylesheets" result)
          "file:<CSS檔案的路徑>")  ;; 將CSS檔案加入到場景的Stylesheets清單中。
     (return result)))

<CSS檔案的路徑>怎麼寫就很需要考慮了。

使用相對路徑就會依賴程式執行的位置,並且不像是User Agent Stylesheet 直接由JavaFX提供了CLI參數的覆寫方式。
使用絕對路徑麻煩,又降低了應用的靈活性。

需要一種折中的靈活方式:具參考的相對路徑,但這個參考位置不能像是程式執行的工作目錄那麼容易改變,需要是定海神針般不會改變的位置。
一個可能的選項是使用者的家目錄,這個選項反倒很適合作為User Agent Stylesheet的預設讀取位置,但不適合程式發佈的環境。
更好的參考位置是程式存在的位置(並非執行位置),以JAVA生態會以CLASS檔案作為參考位置Class.getResource(),但是由ABCL建立的運行時類別(runtime class)並沒有可參考的位置。
另一個可參考的對象是LISP檔案位置,可以透過讀取*load-pathname*或*load-truename*來取得當前正在載入的LISP檔案,但LISP檔案讀取位置也存在不確定的問題,比如透過#'asdf:load-system載入的系統,ASDF會建立快取且由快取位置讀取,而不是原本預期的LISP檔案位置。

並且雖然Common LISP的Pathnames as Filenames規範靈活抽象了,卻不是那麼容易使用,更多的會使用UIOP處理位置問題。

ASDF提供了#'system-source-directory取得系統的定義位置,以及#'system-relative-pathname取得相對於系統的資源位置,使用兩者方式會是更好的處理應用資源存取路徑問題方式。

(defun create-java-info-scene ()
  (prog* (......)
     (#"setAlignment" box (jfield "javafx.geometry.Pos" "CENTER"))
     (#"add" (#"getStylesheets" result)
        (format nil 
                "file:~A"   ;;  透過 `#'system-relative-pathname`取得資源檔案
               (asdf/system:system-relative-pathname :abcl-javafx "res/style.css")))
     (return result)))

除此之外,將res/style.css作為靜態檔案加到abcl-javafx的ASDF System裡面:

(defsystem "abcl-javafx" ......
  :serial t
  :components (......
               (:module "res"
                :components       ;; `res/style.css` 作為靜態檔案加入到ASDF System
                ((:static-file "style.css")))))

以系統資源路徑設定資源存取位置

我使用的res/style.css完整路徑包含空格,需要進一步使用%20替換。更簡單的方式是改使用java.io.File.toURI().toString()處理。

- (format nil "file:~A" (asdf/system:system-relative-pathname :abcl-javafx "res/style.css"))
+ (#"toString"
+   (#"toURI"
+     (new "java.io.File" (namestring (asdf/system:system-relative-pathname :abcl-javafx "res/style.css")))))

這樣子包裝的寫有點難看,JAVA Package提供了#'JAVA:chain的巨集,寫成pipeline的形式:

- (#"toString"
-   (#"toURI"
-     (new "java.io.File" (namestring (asdf/system:system-relative-pathname :abcl-javafx "res/style.css")))))
+ (chain (new "java.io.File" (namestring (asdf/system:system-relative-pathname :abcl-javafx "res/style.css")))
+       "toURI"
+       "toString")

同樣的getStylesheets().add()也可以改寫:

- (#"add" (#"getStylesheets" result)
-         scene-style)
+ (chain (#"getStylesheets" result)
+     ("add" scene-style))

抽出共用資源函式

因為資源存取通常很常見,可以進一步將其提取進utils.lisp,然後使用新定義的#'get-system-resource取得資源路徑:

(in-package :abcl-javafx)

(defun get-system-resource (resource-path)
  (chain (new "java.io.File"
              (namestring (asdf/system:system-relative-pathname
                           :abcl-javafx
                           resource-path)))
         "toURI"
         "toString"))

再運用相同作法幫應用加上圖示:

(defun +MY-APP.start+ (this stage)
  (prog ((java-info-scene (create-java-info-scene)))
    ......
     (chain (#"getIcons" stage)
            ("add" (new "javafx.scene.image.Image" 
                   (get-system-resource "res/icon32x32.png"))))
     (#"show" stage)))

這個圖示檔案本身就是系統資源的一部分,最終會出現在視窗左上角:

https://ithelp.ithome.com.tw/upload/images/20260927/20112470uVRcqdrIaS.png

執行結果

使用 ASDF 系統資源路徑之後,Scene CSS 與視窗圖示都能穩定被載入,應用程式視窗會顯示以 λFX 為風格的系統圖示:

https://ithelp.ithome.com.tw/upload/images/20260927/20112470VIjRaoECQs.png

這表示資源路徑已經能被 ASDF 系統定位,並且圖示檔案成功被 JavaFX 的 Image 物件載入到視窗中。換句話說,這個結果證明我們已經能從系統資源位置取出圖示,而不只是從執行目錄取得檔案。下一篇將進一步設定 JavaFX 元件的 style class 與 style id,讓個別元素可以展示個人化的風格。


上一篇
lesson 4: abclFX怎麼使用CSS?從執行指定Hello MyStyle開始
下一篇
lesson 6: 怎麼設定個別元素風格? 用Style ID & Style Class設定場景風格
系列文
玩轉ABCL Lisp!用奇怪偏門的方式學習JavaFX 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言