日曜プログラミング

休日趣味でやってるプログラミング関連記事をダラダラと

Clojure から無理やり FXML UI を参照する

無理やりシリーズ連続w

今日も今日で飽きもせず JavaFX API を眺めていると Node#id なるプロパティを見つけた。API doc の String ID と言う項目に説明がある。

んん?これ FXML の fx:id と違うんだっけ?お手軽に試すため fx:script を試した時の project を使い回して fx-ui.fxml をこんな感じに変える。

  • resources/fx-ui.xml
<?xml version="1.0" encoding="UTF-8"?>

<?language Clojure?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
  <children>
    <Label fx:id="Ue"    id="Shita" layoutX="59.0" layoutY="124.0" prefHeight="14.0" prefWidth="173.0" text="upper" />
    <Label fx:id="Shita" id="Ue"    layoutX="58.5" layoutY="183.0" prefHeight="14.0" prefWidth="173.0" text="lower" />

    <Button layoutX="75.0" layoutY="67.0" mnemonicParsing="false" text="Hello" onAction="(f)"/>

    <fx:script source="action.clj" />
  </children>
</AnchorPane>

fx:script に指定した action.clj はこんな感じ。同じ UI に fx:id と id それぞれ違う名前で持たせた。

  • resources/action.clj
(defn f []
  (println "[upper] text:" (.getText Ue) ", id:" (.getId Ue))
  (println "[lower] text:" (.getText Shita) ", id:" (.getId Shita) "\n"))

Clojure 内では fx:id は参照できないが API doc 見ると lookup メソッドが id で探してくれるみたいなのでやってみる。

  • src/fxevent/core.clj
(ns fxevent.core
  (:import [javafx.application Application]
           [javafx.fxml FXMLLoader]
           [javafx.scene Scene]
           [javafx.stage Stage])
  (:require [clojure.java.io :as io])
  (:gen-class
   :extends javafx.application.Application))

(defn -start [this ^Stage stage]
  (let [scene (-> "fx-ui.fxml" io/resource FXMLLoader/load)
        ue (.lookup scene "#Ue")
        shita (.lookup scene "#Shita")]

    (println "[upper] text:" (.getText ue) ", id:" (.getId ue))
    (println "[lower] text:" (.getText shita) ", id:" (.getId shita) "\n")

    (doto stage
      (.setScene (Scene. scene))
      (.show))))

(defn -main [& args]
  (javafx.application.Application/launch fxevent.core args))

lookup メソッドは Scene にもあるので取りあえず start メソッド内で。Primary Scene が start メソッド内に引きこもっているのを連れ出す方法は前回で分かってるのでそれと組み合わせればこりゃ結構どうにでもなるか?

結果確認。

fxevent>lein run
[upper] text: lower , id: Ue
[lower] text: upper , id: Shita

[upper] text: upper , id: Shita
[lower] text: lower , id: Ue

最初の 2 行が start 時に仕込んだもので lein run した後すぐ出る。その後の 2 行が fx:script で指定したボタンを押した後に出る奴。見れば text プロパティが意図した通り入れ替わってる事から FXML から読み込んだ UI 部品のインスタンスもちゃんと取れ、fx:id と id プロパティが違う事も分かる。Node の id は CSS のスタイル指定でも適用されるから凝った GUI デザインにする場合バッティングしていやんとなるかもしれないが、そうでなければ別に fx:id 要らなくね?と思った。