简体   繁体   中英

JavaFX from Clojure repl

I start to learn Clojure, and I want to try JavaFX for GUI. I found this article: http://nailthatbug.net/2011/06/clojure-javafx-2-0-simple-app/ , but I want to start it via repl for fast testing and convenience.

So, for instance, I can write in repl this and see new window:

(defn main-start []
  (doto (JFrame. "Window!")
   (.setSize (java.awt.Dimension. 400 300))
   (.setVisible true)))

Is there any way to do such thing with javafx.application.Application - to see new JavaFX window?

Thx. Andrew.

If you read the JavaFX Application class documentation , you will see that the Application class is an abstract class, which cannot be instantiated directly. That means, you at least have to create a subclass of javafx.application.Application.

Life-cycle

The entry point for JavaFX applications is the Application class. The JavaFX runtime does the following, in order, whenever an application is launched:

  1. Constructs an instance of the specified Application class
  2. Calls the init() method
  3. Calls the start(javafx.stage.Stage) method
  4. Waits for the application to finish, which happens when either of the following occur: the application calls Platform.exit() the last window has been closed and the implicitExit attribute on Platform is true Calls the stop() method Note that the start method is abstract and must be overridden.

Therefore you need to generate a class - using the gen-class macro as it can be seen in the blog post - with a start method to be able to start the application.

Edit: Link to example application using the gen-class approach added
I've created a Github repository with a simple example JavaFX application in Clojure . here is the Clojure file following the gen-class approach:

(ns jfx.app
  (:import (javafx.beans.value ChangeListener ObservableValue)
           (javafx.concurrent Worker$State)
           (javafx.event ActionEvent EventHandler)
           (javafx.scene Scene)
           (javafx.scene.control Button)
           (javafx.scene.layout StackPane)           
           (javafx.stage Stage)
           (javafx.scene.web WebView)))

(gen-class
 :name clj.jfx.App
 :extends javafx.application.Application
 :prefix "app-") 

(defn app-start [app ^Stage stage]
  (let [root (StackPane.)
        btn (Button.)
        web-view (WebView.)
        state-prop (.stateProperty (.getLoadWorker (.getEngine web-view)))
        url "http://clojure.org"]

    ;; Add a WebView (headless browser)
    (.add (.getChildren root) web-view)
    ;; Register listener for WebView state changes
    (.addListener state-prop
                  (proxy [ChangeListener] []
                    (changed [^ObservableValue ov
                              ^Worker$State old-state
                              ^Worker$State new-state]
                      (println (str "Current state:" (.name new-state)))
                      (if (= new-state Worker$State/SUCCEEDED)
                        (println (str "URL '" url "' load completed!"))))))
    ;; Load a URL
    (.load (.getEngine web-view) url)

    ;; add a Button with a click handler class floating on top of the WebView
    (.setTitle stage "JavaFX app with Clojure")
    (.setText btn "Just a button")
    (.setOnAction btn
                  (proxy [EventHandler] []
                    (handle [^ActionEvent event]
                      (println "The button was clicked"))))
    (.add (.getChildren root) btn)

    ;; Set scene and show stage
    (.setScene stage (Scene. root 800 600))
    (.show stage)))

(defn app-stop
  "Stop method is called when the application exits."
  [app]
  (println "Exiting application!")
  )

(defn launch
  "Launch a JavaFX Application using class clj.jfx.App"
  []
  (javafx.application.Application/launch clj.jfx.App (into-array String [])))

The jfx.app namespace has to be compiled to launch the application, this will not work if you run the code in the REPL directly. If you want to try the code, follow the instructions for setting up JavaFX with Maven and Leiningen in the project's README.md file .

Though it's still in its infancy, I've been able to use JavaFx from the REPL using Upshot . The main trick is just to completely ignore Application and create your scene directly. To do this you need only force the runtime to initialize and example of which can be seen at core.clj:69 . The other trick is that almost everything you do has to be wrapped in a run-now block to ensure it runs on the JavaFX thread. JavaFX is much more picky about threading than Swing.

Thx a lot Dave. I've also found a solution with javafx.embed.swing.JFXPanel:

(ns to-dell3 
  (:import 
    (javafx.application Application Platform)
    (java.util Date)
    (javafx.scene Group Scene)
    (javafx.scene.text Font Text)
    (javax.swing JFrame SwingUtilities)
    ChartApp1
    javafx.scene.paint.Color
    javafx.embed.swing.JFXPanel))



(defn launch-javafx [] (SwingUtilities/invokeLater 
  (proxy [Runnable] [] (run [] 
                (let [frame2 (JFrame. "JFrame")
                      fxPanel2 (JFXPanel.)
                      ]
                  (do 
                    (.setSize frame2 500 200 )
                    (.setVisible frame2 true)
                    (.setDefaultCloseOperation frame2 JFrame/DISPOSE_ON_CLOSE)
                    (.add frame2 fxPanel2)
                    (Platform/runLater (proxy [Runnable] [] (run [] (let [root2 (Group.)
                                              scene2 (Scene. root2  Color/ALICEBLUE)
                                              text2 (Text.)]
                                          (do
                                            (.setX text2 40)
                                            (.setY text2 100)
                                            (.setFont text2 (Font. 25))
                                            (.setText text2 "Welcome to Clojure + REPL + JavaFX!")
                                            (.add (.getChildren root2) text2)
                                            (.setScene fxPanel2 scene2)
                                            )))))))))))

JavaFX 2.2 needed. And befor this, in REPL: (Platform/setImplicitExit false) This is direct port of this code: Integrating JavaFX into Swing Applications so it looks very imperative and naive, coz Im noob in Clojure world, and may be someone more experienced, rewrite this in more Clojureish way. Anyway, it works for me now, and I think about concept of two launchers: one for repl-based development through (launch-javafx), and one for publish: through normal javafx.application.Application launcher. I still don't know does them equivalent to each other (I mean full JavaFX API availible in case of javafx.embed.swing.JFXPanel), and if so, this is fit for my goal(development through REPL) now. And Im still investigating Upshot code - may be more gentle wey wil be found.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM