3

Referring to this SO question on a first UI program in Clojure, I created a new Leiningen app project:

lein new app a-ui-app

copied the source into the core.clj that leiningen generated and modified the -main routine to call it

(defn -main
  "See https://stackoverflow.com/questions/2792451/improving-my-first-clojure-program?rq=1."
  [& args]
  ;; work around dangerous default behaviour in Clojure
  (alter-var-root #'*read-eval* (constantly false))

  (doto panel
        (.setFocusable true)
        (.addKeyListener panel))

  (doto frame
        (.add panel)
        (.pack)
        (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
        (.setVisible true))

  (loop []
        (draw-rectangle panel @x @y)
        (Thread/sleep 10)
        (recur))
  )

I then run it via either

lein run

or

lein uberjar
java -jar ./target/a-ui-app-0.1.0-SNAPSHOT-standalone.jar 

In both cases, the app works well, but in the terminal that I used to start it up, I get an exception after a random delay of several seconds:

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: No matching clause: 157 at a_ui_app.core$fn__16$fn__21$fn__22.invoke(core.clj:19) at clojure.lang.AFn.call(AFn.java:18) at clojure.lang.LockingTransaction.run(LockingTransaction.java:263) at clojure.lang.LockingTransaction.runInTransaction(LockingTransaction.java:231) at a_ui_app.core$fn__16$fn__21.invoke(core.clj:17) at a_ui_app.core.proxy$javax.swing.JPanel$KeyListener$6c415903.keyPressed(Unknown Source) at java.awt.Component.processKeyEvent(Component.java:6340) at javax.swing.JComponent.processKeyEvent(JComponent.java:2809) at a_ui_app.core.proxy$javax.swing.JPanel$KeyListener$6c415903.processKeyEvent(Unknown Source) at java.awt.Component.processEvent(Component.java:6159) at java.awt.Container.processEvent(Container.java:2083) many more lines...

I made no changes to project.clj -- just used the leiningen-generated one.

I'd like to understand what's going on. I am by no means knowledgeable in Java Threading. Is the problem related to the way leiningen launches the app's Java threads? Is it unavoidable? If not, how can I fix it, both for this little sample program and going forward, as a project pattern for future projects using the UI thread (which I think is AWT-EventQueue-0).

4

1 回答 1

2

我不知道你为什么会得到那个错误,但我会说你做错了几件事: Swing 是一个复杂的野兽;)

Swing 不是线程安全的。关于在 EDT(事件调度线程/UI 线程)上可以做什么和不可以做什么的“规则”随着时间的推移而改变……在某个时候,Sun 决定所有修改 Swing 组件的事情都应该在 EDT 上完成。

因此,从另一个忙于旋转循环的线程中绘制矩形是一个很大的禁忌。此外,您绘制的方式也不正确:您不应该直接获取 Graphics 对象并从其他线程修改它(这是一种超级黑客,应该会触发疯狂的闪烁)。一种“正确”的 Swing 方法是覆盖paintComponent(Graphics g)Java 方法并在那里进行绘图:因此,每次需要重绘该组件时,都会正确地重绘它。

这是使用paintComponent绘制矩形的代码的修改版本(我没有修复嵌套的if语句应该是一个案例等) :

(import java.awt.Color)
(import java.awt.Dimension)
(import java.awt.event.KeyListener)
(import javax.swing.JFrame)
(import javax.swing.JPanel)

(def x (ref 0))
(def y (ref 0))

(def panel
  (proxy [JPanel KeyListener] []
    (paintComponent [g]
      (proxy-super paintComponent g)
      (doto g
        (.setColor (java.awt.Color/WHITE))
        (.fillRect 0 0 100 100)
        (.setColor (java.awt.Color/BLUE))
        (.fillRect (* 10 @x) (* 10 @y) 10 10)))
    (getPreferredSize [] (Dimension. 100 100))
    (keyPressed [e]
      (let [keyCode (.getKeyCode e)]
        (if (== 37 keyCode) (dosync (alter x dec))
        (if (== 38 keyCode) (dosync (alter y dec))
        (if (== 39 keyCode) (dosync (alter x inc))
        (if (== 40 keyCode) (dosync (alter y inc))))))
        (.repaint this)
        ))
    (keyReleased [e])
    (keyTyped [e])))

(def frame (JFrame. "Test"))

(defn -main [& args]

  (doto panel
        (.setFocusable true)
        (.addKeyListener panel))

  (doto frame
        (.add panel)
        (.pack)
        (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
        (.setVisible true)))

我不太喜欢分别deref'ing x 和 y :但是 AFAICTpaintComponent和关键侦听器触发器都保证在 EDT 上发生,因此您的情况下的读数应该是一致的。如果我是你,我仍然会使用单个xydef。

我对 JPanel 也有点困惑,它也是一个 KeyListener 和 -main 函数,然后将 KeyListener 添加到自己:(doto panel (.addKeyListener panel))感觉很奇怪。可能没问题,我不知道:只是感觉很奇怪:)

现在关于您的异常,我不知道,但 Swing EDT 实际上偶尔会抛出异常,因为 Swing 有很多错误,并且因为 Swing 正确使用非常复杂,以至于程序往往会犯很多诚实的错误。根据平台/JVM,要么捕获异常并且 EDT 继续运行,要么自动启动新的 EDT 通常你不应该能够“崩溃” EDT,因为如果 EDT 崩溃它应该会自动重启。这就是为什么您看到异常但您说您的程序仍然“运行良好”的原因。

我会说异常和神秘的堆栈跟踪与 Swing 不是线程安全的并且你做奇怪的事情有关:旋转循环获取面板的底层 Graphics 对象并弄乱它,但我真的不确定。

您进行上述修改的代码似乎正在做您想做的事,而且不会眨眼。

希望能帮助到你。

于 2013-03-24T18:42:12.833 回答