在Leiningen的Clojure UI应用程序中追踪AWT异常

时间:2013-03-24 14:53:20

标签: clojure awt leiningen

参考此SO question on a first UI program in Clojure,我创建了一个新的Leiningen应用项目:

lein new app a-ui-app

将源复制到leiningen生成的core.clj并修改-main例程以将其命名为

(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))
  )

然后我通过

运行它
lein run

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

在这两种情况下,应用程序运行良好,但在我用来启动它的终端中,我在几秒钟的随机延迟后得到异常:

  

线程“AWT-EventQueue-0”中的异常   java.lang.IllegalArgumentException:无匹配子句:157 at   a_ui_app.core $ fn__16 $ fn__21 $ fn__22.invoke(core.clj:19)at 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 at   a_ui_app.core.proxy $ javax.swing.JPanel中$ $ KeyListener的6c415903.keyPressed(未知   来自)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(未知   来自java.awt.Component.processEvent(Component.java:6159)at   java.awt.Container.processEvent(Container.java:2083)还有更多   线...

我没有对project.clj做任何更改 - 只是使用了leiningen生成的那个。

我想了解发生了什么。我不懂Java Threading。问题与leiningen启动应用程序Java线程的方式有关吗?这是不可避免的吗?如果没有,我如何修复它,对于这个小样本程序和未来,作为使用UI线程的未来项目的项目模式(我认为是AWT-EventQueue-0)。

1 个答案:

答案 0 :(得分:2)

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

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分别使用x和y的忠实粉丝:但是AFAICT paintComponent和关键监听器触发器都保证在EDT上发生,所以你的情况下的读数应该是始终如一。如果我是你,我还会使用一个xy def。

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

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

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

上面修改过的代码似乎正在做你想要做的事情而没有眨眼。

希望它有所帮助。