ALGOBIT

2011/09/30

Swinging Clojure (3)

Filed under: 離散的な気まぐれ — タグ: , — Kohyama @ 12:57

Clojure から Swing を使います.
今回は, イベントの処理.

テキストフィールド二つ (TF1, TF2 とします) と, ボタン二つ (B1, B2 とします)を配置.
B1 が押されると, TF1 に書かれた文字列を TF2 にコピー.
B2 が押されると, TF1 と TF2 に書かれた文字列を消去します.
ボタンのラベルは “COPY” と “CLEAR” でも良いのですが, ボタンに表示される文字列と,
内部で使うコマンド用の文字列を区別するために少し長い名前にしてあります.

(import
  (java.awt FlowLayout)
  (java.awt.event ActionListener)
  (javax.swing JButton JFrame JTextField))

(let [
  tf1  (JTextField. 32)
  tf2  (JTextField. 32)
  b1   (JButton. "COPY TF1 to TF2")
  b2   (JButton. "CLEAR BOTH")
  f    (proxy [JFrame ActionListener]["Swinging Clojure (3)"]
         (actionPerformed [ae]
           (let [c (.getActionCommand ae)]
             (cond (= c "COPY") (.setText tf2 (.getText tf1))
                   (= c "CLEAR") (dorun (.setText tf1 "")
                                        (.setText tf2 ""))))))
  pane (.getContentPane f)]

  (.setLayout pane (FlowLayout.)) 

  ; commands of buttons
  (dorun (map
    (fn [[button command]]
      (doto button
        (.addActionListener f)
        (.setActionCommand command)))
    [[b1 "COPY"] [b2 "CLEAR"]]))

  ; layout
  (dorun (map #(.add pane %) [tf1 tf2 b1 b2]))

  ; frame
  (doto f
    (.setSize 400 300)
    (.setLocationByPlatform true)
    (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE)
    (.setVisible true)))

10行目 proxy は
(proxy [スーパークラス インターフェイス1 インターフェイス2 ... ] [スーパークラスのコンストラクタへの引数]
(メソッド名 [仮引数] ボディ)
… )
のように書きます.
スーパークラスを extends し, インターフェイスを implements する無名のクラス(内部的には clojure が他と重複しないよう, 適当な名前を付けてくれています.) を作成し, そのインスタンスを new して返してくれます.
インターフェイスに必要な全てのメソッドを定義しなくても構いません.
ここでは, JFrame を extends し, ActionListener を implements するクラス(
actionPerformed で ActionEvent に対する処理を記述します)を無名で作成し, そのインスタンスを f に束縛しています.
23行目で, 二つのボタンにこの f を addActionLisner でリスナーとして登録していますので, ボタンが押されたときは, proxy に書いておいた actionPerformed が(ActionEvent のインスタンスを引数として)呼ばれます.
その際, setActionCommand でボタンに登録しておいた文字列を, 12行目 getActionCommand で ActionEvent から取り出して, 処理を分岐しています.

proxy では,「スーパークラス」を省略することもできます(デフォルトで Object を継承します)ので, JFrame に ActionListener をさせずに, 別途 ActionListener の機能だけを持ったクラスのインスタンスを作り, ボタンのリスナーとして登録する場合は以下のようになります.

(import
  (java.awt FlowLayout)
  (java.awt.event ActionListener)
  (javax.swing JButton JFrame JTextField))

(let [
  f    (JFrame. "Swinging Clojure (3)")
  pane (.getContentPane f)
  tf1  (JTextField. 32)
  tf2  (JTextField. 32)
  b1   (JButton. "COPY TF1 to TF2")
  b2   (JButton. "CLEAR BOTH")
  al   (proxy [ActionListener][]
         (actionPerformed [ae]
           (let [c (.getActionCommand ae)]
             (cond (= c "COPY") (.setText tf2 (.getText tf1))
                   (= c "CLEAR") (dorun (.setText tf1 "")
                                        (.setText tf2 ""))))))]

  (.setLayout pane (FlowLayout.))

  ; commands of buttons
  (dorun (map
    (fn [[button command]]
      (doto button
        (.addActionListener al)
        (.setActionCommand command)))
    [[b1 "COPY"] [b2 "CLEAR"]]))

  ; layout
  (dorun (map #(.add pane %) [tf1 tf2 b1 b2]))

  ; frame
  (doto f
    (.setSize 400 300)
    (.setLocationByPlatform true)
    (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE)
    (.setVisible true)))

Swinging Clojure (2)

Filed under: 離散的な気まぐれ — タグ: , — Kohyama @ 10:39

Clojure から Swing を使います.
今回は, レイアウトの適用.

(import '(javax.swing JFrame JButton))
(import 'java.awt.BorderLayout)
(let [f (JFrame. "Swinging Clojure (2)")
      p (. f getContentPane)]
  (. p setLayout (BorderLayout.))
  (dorun
    (map
      (fn [[c l]] (. p add c l))
      [[(JButton. "PAGE_START") BorderLayout/PAGE_START]
       [(JButton. "LINE_START") BorderLayout/LINE_START]
       [(JButton. "CENTER")     BorderLayout/CENTER]
       [(JButton. "LINE_END")   BorderLayout/LINE_END]
       [(JButton. "PAGE_END")   BorderLayout/PAGE_END]]))
  (doto f
    (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
    (.setLocationByPlatform true)
    (.setSize 400 300)
    (.setVisible true)))

6行目〜 (dorun (map のところは,

(. p add (JButton. "PAGE_START") BorderLayout/PAGE_START)
(. p add (JButton. "LINE_START") BorderLayout/LINE_START)
(. p add (JButton. "CENTER")     BorderLayout/CENTER)
(. p add (JButton. "LINE_END")   BorderLayout/LINE_END)
(. p add (JButton. "PAGE_END")   BorderLayout/PAGE_END)

と同じで, これだけだと全部書いても記述量は変わりませんが, 共通部分をまとめる練習ということで.
map は遅延評価リストを返すので, そのままでは map が返した結果を直接または間接に出力しようとしないと, 中身は評価されません.
評価を強制するには dorun か doall を使います.
副作用のみが目的の今回は, map の返すリストを返さない dorun を使います.
(doall は map が返す結果を返します.)

13行目〜 doto [x & forms] は, x を forms の全てのフォームの二番目(つまりフォームが関数呼び出しなら, 最初の引数) の要素に挿入してくれる. つまり,

(.setDefaultCloseOperation f JFrame/EXIT_ON_CLOSE)
(.setLocationByPlatform f true)
(.setSize f 400 300)
(.setVisible f true))

ということ.
これだけだと記述量は変わらないけど, 練習ということで.

(. f setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
(. f setLocationByPlatform true)
(. f setSize 400 300)
(. f setVisible true))

と書いても同じ.

BorderLayout やその他のレイアウトの詳細は
How to Use BorderLayout の辺りが分かりやすいです.

Swinging Clojure (1)

Filed under: 離散的な気まぐれ — タグ: , — Kohyama @ 10:02

Clojure のいいところの一つは, 組み込みの GUI ツールキットがあるところです.
その名も Swing.
JVM の上で動き, Java のクラスライブラリが使えるため, javax.swing ももちろん使える訳です.
なるほど.

まずは, 最小限コード.
ラベル一つを貼付けたウィンドウを開くだけ.

(import '(javax.swing JFrame JLabel))

(let [f (JFrame. "Swinging Clojure (1)")
      p (. f getContentPane)]
  (. p add (JLabel. "Hello Swinging Clojure!"))
  (. f setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
  (. f setLocationByPlatform true)
  (. f setSize 200 100)
  (. f setVisible true))

6 〜 8行目 はこの時点でどうしても必要というものではありませんが, ウィンドウを開く時の基本設定ですので, やっつけときます.
setDefaultCloseOperation で, ウィンドウが閉じられた時の挙動を指定します. EXIT_ON_CLOSE は, exit で, アプリケーションを終了します. REPL で実行しているならば REPL が終了します.
デフォルトは HIDE_ON_CLOSE で, リソースを解放せずに, ウィンドウを隠します. setVisible(false) 相当ですので, 後で setVisible(true) すればもう一度ウィンドウを開けます.
デフォルトでは毎回同じ位置にウィンドウが開きます. 大抵のウィンドウシステムは, ウィンドウを開くときに少しずつずれた位置に表示して, 完全に重ならないようにしてくれる機能がありますが, この機能を有効にするには, setLocationByPlatform() に true を指定します.
setSize は名前から想像できる通りです.

Swing は知っています. という方には以下の java コードとほぼ同等と思っていただければ良いかと思います.
Hello.java

import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class Hello {
    public static void main(String[] args) {
        JFrame f = new JFrame("Swinging Clojure (1)");
        Container p = f.getContentPane();

        p.add(new JLabel("Hello Swinging Clojure!"));
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLocationByPlatform(true);
        f.setSize(200, 100);
        f.setVisible(true);
    }
}

Copyright © 2010 Yoshinori Kohyama All Rights Reserved.