参考此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
)。
答案 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>
上面修改过的代码似乎正在做你想要做的事情而没有眨眼。
希望它有所帮助。