Getting started with JavaFX in Clojure
Prerequisites
A JVM, Leiningen, Maven and the jfxrt.jar (described below)
The jfxrt.jar
First we have to locate the jfxrt.jar, since it is not by default on the classpath. If you're using Windows or OS X or use the Oracle JVM on Linux it is simple to locate; It's inside the lib folder of the JRE directory.
For OpenJDK, you'll need the JavaFX Scene Builder 1.1 Developer Preview and install the delivered jfxrt.jar. You'll probably have to copy the whole lib folder distributed with the Scene Builder and partially replace the OpenJDK one.
Now use the following command to add JavaFX to your local Maven repository, and replace $JFXRTPATH with the path to the JRE lib folder:
mvn install:install-file \
-Dfile=$JFXRTPATH/jfxrt.jar \
-DgroupId=com.oracle \
-DartifactId=javafx-runtime \
-Dpackaging=jar \
-Dversion=2.2.0
You can now reference it in your Leiningen dependencies with
[com.oracle/javafx-runtime "2.2.0"]
The code
First I have to say the folks at Oracle really did a great job designing the API. It's much more straightforward to use and feels almost native in Clojure, especially when using the *Builder classes.
but there are a few things you'll want to do before using it:
- Don't use javafx.application.Application. That would mean to extend a class, and fortunately it is not necessary. Just add this line to your code:
(defonce force-toolkit-init (javafx.embed.swing.JFXPanel.))
- Create a shortcut to Platform.runLater. Really, this'll save you a lot of typework:
(defn run-later*
[f]
(javafx.application.Platform/runLater f))
(defmacro run-later
[& body]
`(run-later* (fn [] ~@body)))
(defn run-now*
[f]
(let [result (promise)]
(run-later
(deliver result (try (f) (catch Throwable e e))))
@result))
(defmacro run-now
[& body]
`(run-now* (fn [] ~@body)))
(defn event-handler*
[f]
(reify javafx.event.EventHandler
(handle [this e] (f e))))
(defmacro event-handler [arg & body]
`(event-handler* (fn ~arg ~@body)))
(Code snippets borrowed from the abandoned upshot library)
Example application See this gist for a small, REPL-compatible(!) hello-world-application (including project.clj).
How do I know when I have to use
run-now
/run-later
? For starting out, it's needed for Stage and Scene. Additionally you will get an "IllegalStateException Not on FX application thread" if you have some code that belongs inside a run-now or run-later.Is there an actively developed library? Probably I'll work on one soon, in case I depend on JavaFX for my next project. I've started writing a little wrapper with convenience functions; You can find it here.
Further links
Written by Daniel Ziltener
Related protips
9 Responses
Daniel,
It's so great to see that you, too, are playing around with Clojure and JavaFX. I've been using chrix75's clj-javafx project on GitHub. How does your project (clojurefx) compare?
Also, I don't know if you've been getting this problem (maybe it's because I'm using JavaFX 8 instead of 2.2), but I keep trying to do:
(import 'javafx.scene.control.Label)
and I keep getting:
NoClassDefFoundError javafx.scene.control.Labeled
This is true whenever I try to import anything in the javafx.scene.control class. Are you by chance getting the same error?
Alex
Alex,
I'm working quite much on my library right now, I've pushed a new version just yesterday evening (and plan to push another heap of new stuff today). I'm working with Java(FX) 8 now for it, and everything works fine (OpenJDK 8 b113). It's probably a bit early to really fully compare the two.
I aim to have mine being easily extensible even if you're just using the library.
I tried:
(deffx somelabel label :text "Test")
which worked just fine here.
Daniel,
Thanks for your speedy response. To be honest, I haven't actually tried your project out yet - I've just been using clj-javafx. I'll try yours later today and see how it works out. I'll let you know how it goes!
Alex,
Ok; Just keep in mind it is brand-new and still lacks a lot (this will however be corrected soon since I need the lib for a project ;) ).
I've just released 0.0.6 for which most element creation, events and unidirectional property binding is working.
That's totally fine haha. What project, may I ask? Any screenshots? ;) I love UI design... Unless, of course, you're just working on the application logic so far, which is understandable given the (pre-)alpha stage of clojurefx.
It's becoming a database frontend, but I didn't start the UI yet - I first need that ClojureFX as a foundation to continue my work. And then my API decisions will have to prove themselves...
That makes sense, of course, to have that foundation. I started playing around with some UI elements (different fonts, layouts, buttons, etc. - the usual) using clj-javafx but it seemed like everything would break down at some point. Fonts wouldn't load correctly; the CSS files wouldn't quite apply to everything; some elements wouldn't import correctly... it was kind of a nightmare. And to make matters worse, the guy who was working on clj-javafx hadn't done any commits in months. I think he had abandoned the project. So it's nice to have someone who's working on it still :)
Also, I'm still a beginner with Clojure (although I'm decent at Scheme... SICP and all that), but if you ever need/want me to, I'm totally willing to contribute to ClojureFX if you'd like. Until then I'll just be kind of messing around with it and seeing how it works, etc.
P.S. - I can't wait until Oracle finally releases a "real" JRE (not just ADF Mobile) + JavaFX for iOS and Android. It'll be so cool to see what I'm developing on my Mac be interactive on an iPad (without hacks like Splashtop Streamer).
EDIT
Having looked at the code more (btw, the whole Labeled class import problem disappeared by using your deffx macro... yay), I feel utterly incompetent to really contribute anything meaningful. I think macros are like... 10 chapters away in the Clojure book I'm working through. Maybe someday... Also, I have to say that your wrapper is already much more elegant than the clj-javafx. Looks like you've automatically included many of the possible JavaFX classes and objects... clj-javafx never did that. +1
I like Clojurefx a lot. One thing I don't like, though, is the dependency on Swing. Using JFXPanel starts up the Swing event dispatch thread and consumes quite a bit of RAM even if you don't actually do anything else with Swing. Also I think it might have an adverse effect on the overall rendering performance, though I don't really know very deeply how this works. I'm planning to use JavaFX for, among other things, rendering hd video and overlaying some semi-transparent views on top of it, so performance is a real concern for me. For my small proof of concept I did something akin to what clj-javafx does and I think that looks clean enough..
Hi! Is your example valid for JavaFX 8+ too? As I can see SceneBuilder exists only in version 2.2 of JavaFX :(