使用Clojure repl调用JavaFX

11
我开始学习Clojure,并想尝试JavaFX用于GUI。我找到了这篇文章:http://nailthatbug.net/2011/06/clojure-javafx-2-0-simple-app/,但我想通过repl来启动它以进行快速测试和方便性。
因此,例如,我可以在repl中编写以下内容并查看新窗口:
(defn main-start []
  (doto (JFrame. "Window!")
   (.setSize (java.awt.Dimension. 400 300))
   (.setVisible true)))

有没有办法使用javafx.application.Application实现这样的功能 - 查看新的JavaFX窗口?

谢谢。安德鲁。


你怎么启动repl?由于JavaFX需要的运行时依赖,jfxrt.jar必须在类路径中。你是否使用Leiningen创建了一个项目? - raju-bitter
3个回答

10
如果您阅读JavaFX应用程序类文档,您将看到Application类是一个抽象类,不能直接实例化。这意味着,您至少需要创建javafx.application.Application的子类。
生命周期
JavaFX应用程序的入口点是Application类。每当启动应用程序时,JavaFX运行时按照以下顺序执行:
1. 构造指定Application类的实例 2. 调用init()方法 3. 调用start(javafx.stage.Stage)方法 4. 等待应用程序完成,发生以下情况之一:应用程序调用Platform.exit()、最后一个窗口已关闭且Platform上的implicitExit属性为true、调用stop()方法。请注意,start方法是抽象的,必须被覆盖。
因此,您需要使用gen-class宏生成一个类来启动应用程序(可以在博客文章中看到)。
编辑:添加了使用gen-class方法的示例应用程序链接 我创建了一个简单的JavaFX应用程序的Github存储库,其中包括遵循gen-class方法的Clojure文件。
(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 [])))

jfx.app 命名空间必须编译后才能启动应用程序,如果直接在 REPL 中运行代码则无法工作。如果您想尝试该代码,请按照 项目的 README.md 文件中使用 Maven 和 Leiningen 配置 JavaFX 的说明进行操作。


那么,这意味着没有办法从repl启动JavaFX应用程序,对吗? - Andrew
我猜你可以使用 proxy 来创建一个继承自应用程序的类,并使用该对象。 - Ankur
1
我尝试使用代理类,但没有成功。我进行了一个小测试来确认:当在类构造函数完成之前调用抽象方法时,代理方法将不会被Clojure提供得足够早。这意味着gen-class是唯一的选择。 - raju-bitter
gen-class 无法在 REPL 中使用,因为包含 gen-class 的源代码需要存储在文件中,据我所知。这意味着,如果您至少在磁盘上创建一个 Clojure 文件,则可以使用 Clojure 启动 JavaFX 应用程序。 - raju-bitter
在我的情况下,使用JavaFX 8和Clojure 1.7 alpha,我需要使用(resolve 'clj.jfx.App)而不是clj.jfx.App。我的理由(尚未审核,但似乎合理)是Java类在编译之后才会存在,因此我希望确保读取成功。 - David J.

8
虽然JavaFx仍处于初级阶段,但我已经能够使用Upshot从REPL中使用它。关键是完全忽略Application并直接创建场景。为此,您只需要强制运行时初始化,可以在core.clj:69中看到示例。另一个技巧是几乎所有操作都必须包装在run-now块中,以确保它在JavaFX线程上运行。与Swing相比,JavaFX对线程要求更严格。

3

非常感谢,Dave。 我也找到了一个使用 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版本。在此之前,需要在REPL中输入以下代码:(Platform/setImplicitExit false)。这是将以下代码Integrating JavaFX into Swing Applications直接移植过来的,因此看起来非常命令式和幼稚,因为我在Clojure世界中是新手,也许有更有经验的人可以用更多的Clojure方式重写。无论如何,现在它对我很有效,我考虑两个启动器的概念:一个是基于REPL进行开发的(launch-javafx),另一个是通过正常的javafx.application.Application启动器进行发布。我仍然不知道它们是否相互等效(我的意思是在javafx.embed.swing.JFXPanel的情况下是否可用完整的JavaFX API),如果是这样,那么现在它适合我的目标(通过REPL进行开发)。我仍在调查Upshot代码-可能会找到更温和的方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接