Clojure-JavaFX Application

At the beginning, it is difficult to get everything up and running with Clojure and JavaFX, specially starting the JavaFX thread, you may be familiar with the not very welcomed message “Toolkit not initialized”.

After few try-error configurations, I have reached a stable one.
This configuration allows the compilation and execution of the application using IntelliJ+Cursive and also the execution of the standalone uberjar generated by Leiningen doing just a double click on the jar.

In order to achieve this goal, we need to give the following structure to the core.clj and project.clj

;core.clj
(ns your-project.core
   (:gen-class :extends javafx.application.Application)
   (:require ...
             [your-project.view.mainwindow :as main-window]
             ...)
   (:import [javafx.application Application Platform]))


(defn -start [this stage]
   ...
   (main-window/construct-view stage)
   ...)


(defn -main [& args]
   (Application/launch (Class/forName "your-project.core") (into-array String [])))

Since core.clj extends Application, two functions must be implemented. -start, -main.
The stage can be passed to other namespaces.

;project.clj
(defproject your-project "0.2.1"
   ...
   :main your-project.core
   :profiles {:uberjar {:aot :all}}
   :repl-options {:init (do (compile 'your-project.core)
                            (future (-main)))})

It is necessary to indicate the main class, in this case, your-project.core
For the REPL options we make sure we compile the main class.
The future launches the application and does not block the REPL so you can interact with the running app.

2 thoughts on “Clojure-JavaFX Application

  1. First all thanks for the tutorial, it helped a lot, but can I ask you how can I refresh/eval the application so the change that I make on the code reflect in the window?. I am using cursive and loading the whole namespace again and again but the window do not change. Can you help me with that?

    • Hi Jesus,

      Sorry for the delay, I have been busy with my new job. Not time for trading, nor for Clojure.

      I use IntelliJ with Cursive plugin. I launch the application and I go to the REPL.

      What I do is, I create two def ‘my-stage’ and ‘my-scene’ in the namespace ‘your-project.view.mainwindow’, so that when I launch it, I can navigate to the namespace and use ‘my-stage’ to hide/show the app and ‘my-scene’ for updating and refreshing the styles, etc.

      In that way I can even get the children of my-scene and execute functions in them.

      Do not forget that I bind properties to atoms, like the title for the stage to a i18n atom (code below); in that way, I just need to navigate to the i18n namespace and update that atom to see the change in the UI.

      The framework I use, ‘clojurefx’ also has some function to do something like (setfx my-stage :title “MyNewTitle”)

      ;Interaction once launched
      (ns your-project.view.mainwindow)
      
      (run-now (doto (.getStylesheets my-scene)
                  .clear
                  (.add (.toExternalForm (io/resource "standard.css")))))
      
      ; Where to declare the stage and scene to interact with the app
      (ns your-project.view.mainwindow
      ...
         (defn construct-view [stage on-close]
            (let [main-window (fx v-box :id "main-window"
                                        :children [ ... ])])
                  scene (fx scene :root main-window)]
               ;TODO remove this two, handy for styling and interaction with REPL
               (def my-stage stage)
               (def my-scene scene)
               (doto stage
                  (bind-property! :title (i18n/get-atom :app-title))
                  (set-listener! :on-close-request [_] (on-close))
                  (setfx :min-height 480)
                  (setfx :max-height 580)
                  (setfx :min-width 940)
                  (setfx :max-width 1050)
                  (setfx :scene scene))
               (-> (getfx scene :stylesheets) (.add (.toExternalForm (io/resource "standard.css"))) run-now)
               (-> stage .show run-now)))
      

Leave a Reply

Your email address will not be published. Required fields are marked *

*