2012年2月2日木曜日

ClojureとFXML

JavaFX for Linux キタ━(゚∀゚)━!
ということでさわってみました。

ただ動かすのも面白くない、というかJava面白くない、ということでClojureから呼び出してみました。

JavaFXで私が一番注目していたものはFXML。
XMLは好きじゃないけど、ちゃんとロジックが分離できそうだし、GWTのUiBinderを気に入っていたので楽しみにしていました。

というわけでClojure+FXMLで書いてみました。

プロジェクトの設定(Linux)

Leiningenを使います。

多分Winの場合はそのまま動く。
Linuxの場合は http://www.oracle.com/technetwork/java/javafx/downloads/devpreview-1429449.html から Linux 32-bit をダウンロードして、 lib に jfxrt.jar を、 bin に *.so を置いて下さい。

main.clj

まずはApplicationを継承した起動ポイントを書く。

(ns fx.main
[:gen-class
:extends javafx.application.Application]
[:import
[javafx.application Application]
[javafx.scene Scene]]
[:use
[fx.pane :only [root]]])
(defn -start [this stage]
(let [scene (Scene. (root))]
(doto stage
(.setTitle "test")
(.setScene scene)
(.show))))
(defn -main [& args]
(Application/launch fx.main args))
view raw main.clj hosted with ❤ by GitHub

main/launchだとエラーが出る謎。

pane.clj

xmlをFXMLLoaderでロードするだけ。

(ns fx.pane
[:import
[javafx.fxml FXMLLoader]])
(defn root []
(let [fxml (.getResource (class root) "root.xml")]
(FXMLLoader/load fxml)))
view raw pane.clj hosted with ❤ by GitHub

root.xml

fx:controllerを指定してButtonを置いてonActionを設定する。

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<BorderPane xmlns:fx="http://javafx.com/fxml"
fx:controller="fx.controller">
<center>
<Button text="Test" onAction="#action" />
</center>
</BorderPane>
view raw root.xml hosted with ❤ by GitHub

controller.clj

Buttonから呼び出される関数を定義する。

(ns fx.controller
[:gen-class
:methods [[action [javafx.event.ActionEvent] void]]]
[:import
[javafx.fxml FXML]])
(defn -action [this e]
(let [button (.getSource e)]
(.setText button "Action!")))
view raw controller.clj hosted with ❤ by GitHub

gen-classに:methodsをわざわざ書くのがめんどう。

実行



なんかあっさり動いてしまった。

これでバリバリClojureでGUIが書けるぞー










と思ったらそんなことなかった。

fx:id

Buttonを押したらLabelのテキストが変わるようにしてみたい。
そんな場合はfx:idでControlに名前をつけるとコントローラで扱えるようになる。
その時にコントローラはmutableFXMLアノテーションが付いたフィールドを持っていなければならない。

gen-classでフィールドは指定できない。
Clojureでは:stateを使うことで状態を実現している。

一番最初に思いついたものはdeftypeを使う方法。

deftype

deftypeではmutableなフィールドを持てます。
なのでdeftypeでコントローラを作ってみます。

(defprotocol Action
(action [this e]))
(deftype Controller [^{:unsynchronized-mutable true} label]
Action
(action [this e]
(.setText label "Action!")))
view raw Controller.clj hosted with ❤ by GitHub

でもこれだと、コンストラクタが引数をとるのでFXMLLoaderがインスタンスを作れない。
なのでこれを更に継承・・・・・出来なかった。
なぜならdeftypeで作ったクラスはfinalが修飾されている。
メソッドの型も曖昧なままなので継承できたとしても動くかどうかはわからない。

こうなると別の方法を取るしかないですね。

別の方法といってもClojureでclassを作るにはdeftype, defrecord, gen-classしかない。
deftypeは先程の通りコンストラクタが引数をとってしまう。
defrecordではそもそもmutableなフィールドを持てない。
gen-classはフィールドを指定できない。

Javaを使うことを・・・強いられてるんだ!

ここで登場Java!
嫌いじゃないけど使いたくないJava!

Java側でmutableでFXMLアノテーションが付いた変数を宣言しておこうという戦略。

package fx;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
abstract class Controller {
@FXML private Label label;
}
view raw Controller.java hosted with ❤ by GitHub

これを継承してさあ実行。


javafx.fxml.LoadException: Value is already set.


\(^o^)/

ヤンナルネ・・・・・

結果

onActionで関数を指定して呼び出すことはできたがfx:idが使えなかった。

誰か解決方法があったら教えて下さい!

※追記

javafx.fxml.LoadExceptionは別のところでした・・・
しかし、結果は変わらず。

FXML側で実行すると、エラーが握りつぶされてしまうのですね。

※※追記

解決しました!

0 件のコメント:

コメントを投稿