3

Clojureの最初のUIプログラムに関するこのSOの質問を参照して、新しいLeiningenアプリプロジェクトを作成しました。

lein new app a-ui-app

ソースをcore.cljそのleiningenが生成したものにコピーし、-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 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)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)さらに多くの行...

私は変更を加えませんでしたproject.clj-ライニンゲンで生成されたものを使用しただけです。

何が起こっているのか理解したいのですが。私はJavaスレッドに精通しているわけではありません。問題は、leiningenがアプリのJavaスレッドを起動する方法に関連していますか?やむを得ないですか?そうでない場合は、この小さなサンプルプログラムと今後の両方で、UIスレッドを使用する将来のプロジェクトのプロジェクトパターンとして、どのように修正できますか(私はそう思いますAWT-EventQueue-0)。

4

1 に答える 1

2

なぜそのエラーが正確に発生するのかはわかりませんが、いくつかのことを間違って行っていると思います。スイングは複雑な獣です。)。

Swingはスレッドセーフではありません。EDT(イベントディスパッチスレッド/ UIスレッド)で実行できることと実行できないことに関する「ルール」は、時間の経過とともに変化しました...ある時点で、SunはSwingコンポーネントの変更はすべてEDTで実行する必要があると判断しました。

したがって、回転ループの実行で忙しい別のスレッドから長方形を描画することは、大したことではありません。また、描画の方法も正しくありません。Graphicsオブジェクトを直接フェッチして、他のスレッドから変更することは想定されていません(これは一種のスーパーハックであり、クレイジーな点滅をトリガーするはずです)。これを行う「正しい」Swingの方法の1つは、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)))

私はderefxとyを別々に使用することはあまり好きではありませんが、AFAICTpaintComponentとキーリスナートリガーの両方がEDTで発生することが保証されているため、ケースの読み取り値は一貫している必要があります。私があなただったら、私はまだ単一のxydefを使用します。

また、KeyListenerでもあるJPanelと、KeyListenerを自分自身に追加する-main関数についても少し戸惑っています(doto panel (.addKeyListener panel))。奇妙な感じがします。それは大丈夫かもしれません、私は知りません:それはただ奇妙に感じます:)

例外についてはわかりませんが、Swingにはかなりの数のバグがあり、Swingは正しく使用するのが非常に複雑であるため、プログラムがかなりの数の正直な間違いを犯す傾向があるため、SwingEDTは実際に時々例外をスローします。プラットフォーム/JVMに応じて、例外がキャッチされてEDTが実行を継続するか新しいEDTが自動的に開始されます。通常、EDTがクラッシュすると自動的に再起動するため、EDTを「クラッシュ」させることはできません。そのため、例外が表示されているにもかかわらず、プログラムは「正常に機能している」と言っています。

例外と不可解なスタックトレースは、Swingがスレッドセーフではなく、奇妙なことをしていることに関連していると思います。回転ループがパネルの基になるGraphicsオブジェクトをフェッチし、それをいじりますが、私にはよくわかりません。

上記の変更を加えたコードは、まばたきすることなく、やりたいことをうまく実行しているようです。

それが役に立てば幸い。

于 2013-03-24T18:42:12.833 に答える