clojure GUI編程-2
1 簡介
接上一篇GUI開發,每次手寫GUI佈局代碼比較不方便,能夠使用netbeans的form designer設計好界面,而後從clojure中加載界面,綁定事件來進行GUI設計。 css
2 實現過程
因爲要編譯java代碼,使用leiningen進行項目管理比較方便。先建立一個空項目, lein new okex 建立項目。 html
2.1 添加依賴
修改項目文件夾下的project.clj以下 java
1: (defproject okex "0.1.0-SNAPSHOT" 2: :description "FIXME: write description" 3: :url "http://example.com/FIXME" 4: :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" 5: :url "https://www.eclipse.org/legal/epl-2.0/"} 6: 7: ;; 使用utf-8編碼編譯java代碼,默認會使用windows系統的默認編碼gbk 8: :javac-options ["-encoding" "utf-8"] 9: :java-source-paths ["src"] 10: 11: :dependencies [[org.clojure/clojure "1.10.0"] 12: [com.cemerick/url "0.1.1"] ;; uri處理 13: [slingshot "0.12.2"] ;; try+ catch+ 14: [com.taoensso/timbre "4.10.0"] ;; logging 15: [cheshire/cheshire "5.8.1"] ;; json處理 16: [clj-http "3.9.1"] ;; http client 17: [com.rpl/specter "1.1.2"] ;; map數據結構查詢 18: [camel-snake-kebab/camel-snake-kebab "0.4.0"] ;; 命名轉換 19: [seesaw "1.5.0"] ;; GUI框架 20: ] 21: :main ^:skip-aot okex.core 22: :aot :all 23: :target-path "target/%s" 24: :repl-options {:init-ns okex.core})
2.2 複製文件
把上一篇建立的core2.clj和api.clj複製到src/okex文件夾下,更名core2.clj爲core.clj。 並修改命名空間與文件名對應。 python
2.3 設計gui界面
使用netbeans新建JFrame form,並設計窗體,修改要用到的widget的name屬性爲對應的swing id名。 git
而後保存這個文件到src/okex文件夾下,注意包名要用okex。窗體設計器自動生成的DepthWindow.java。 github
2.4 clojure中加載java gui代碼
修改core.clj,導入gui界面的類,並加載,代碼以下: sql
1: (ns okex.core 2: (:require [seesaw.core :as gui] 3: [seesaw.table :as table] 4: [seesaw.bind :as bind] 5: [seesaw.selector :as selector] 6: [seesaw.table :refer [table-model]] 7: [okex.api :as api] 8: [taoensso.timbre :as log]) 9: (:use com.rpl.specter) 10: (:gen-class) 11: (:import okex.DepthWindow)) 12: 13: 14: ;;;;;;;;;;;;;;;;;;;;; Window-Builder binding 15: 16: (defn identify 17: "設置root下全部控件的seesaw :id 18: 只要有name屬性的,所有綁定到id" 19: [root] 20: (doseq [w (gui/select root [:*])] 21: (if-let [n (.getName w)] 22: (selector/id-of! w (keyword n)))) 23: root) 24: 25: ;;;;;;;;;;;;;;;;;;;;;; 初始化值 26: 27: (def coin-pairs "全部交易對信息" (api/get-instruments)) 28: (def base-coins "全部基準貨幣" 29: (-> (select [ALL :base-currency] coin-pairs) 30: set 31: sort)) 32: 33: (defn get-quote-coins 34: "獲取基準貨幣支持的計價貨幣" 35: [base-coin] 36: (select [ALL #(= (:base-currency %) base-coin) :quote-currency] coin-pairs)) 37: 38: (defn get-instrument-id 39: "根據基準貨幣和計價貨幣得到幣對名稱" 40: [base-coin quote-coin] 41: (select-one [ALL 42: #(and (= (:base-currency %) base-coin) 43: (= (:quote-currency %) quote-coin)) 44: :instrument-id] 45: coin-pairs)) 46: 47: ;; 設置form的默認值 48: (let [first-base (first base-coins)] 49: (def coin-pair-data (atom {:base-coin first-base 50: :quote-coin (-> (get-quote-coins first-base) 51: first)}))) 52: 53: ;;;;;;;;;;;;;;;;;;;;;; 服務 54: (def instruments-info "交易對的深度數據"(atom {})) 55: 56: (defn run-get-instrument-services! 57: "啓動獲取交易對深度信息的服務 58: 沒有提供中止功能" 59: [instrument-id] 60: (when (and instrument-id 61: (not (contains? @instruments-info instrument-id))) 62: (future (loop [] 63: (let [data (api/get-spot-instrument-book instrument-id)] 64: (setval [ATOM instrument-id] data instruments-info)) 65: (Thread/sleep 200) 66: (recur))))) 67: 68: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 輔助函數 69: 70: (defn depth-data-model 71: "深度數據table模型" 72: [data] 73: (table-model :columns [{:key :pos :text "價位"} 74: {:key :price :text "價格"} 75: {:key :amount :text "數量"} 76: {:key :order-count :text "訂單數"}] 77: :rows data)) 78: 79: (defn update-quote-coin-model! 80: "更新計價貨幣的模型" 81: [f model] 82: (let [quote-coin (gui/select f [:#quote-coin])] 83: (gui/config! quote-coin :model model))) 84: 85: (defn get-current-instrument-id 86: "獲取當前幣對的id" 87: [] 88: (let [coin-p @coin-pair-data] 89: (get-instrument-id (:base-coin coin-p) 90: (:quote-coin coin-p)))) 91: 92: (defn bind-transfrom-set-model 93: [trans-fn frame id] 94: (bind/bind 95: (bind/transform #(trans-fn %)) 96: (bind/property (gui/select frame [id]) :model))) 97: 98: (defn add-behaviors 99: "添加事件處理" 100: [root] 101: (let [base-coin (gui/select root [:#base-coin]) 102: quote-coin (gui/select root [:#quote-coin])] 103: ;; 基準貨幣選擇事件綁定 104: (bind/bind 105: (bind/selection base-coin) 106: (bind/transform get-quote-coins) 107: (bind/tee 108: ;; 設置quote-coin的選擇項 109: (bind/property quote-coin :model) 110: (bind/bind 111: (bind/transform first) 112: (bind/selection quote-coin)))) 113: 114: ;; 綁定基準貨幣和計價貨幣的選擇事件 115: (bind/bind 116: (bind/funnel 117: (bind/selection base-coin) 118: (bind/selection quote-coin)) 119: (bind/transform (fn [[base-coin quote-coin]] 120: {:base-coin base-coin 121: :quote-coin quote-coin})) 122: coin-pair-data) 123: 124: ;; 綁定交易對深度信息, 一旦更改就更新depth-view 125: (bind/bind 126: instruments-info 127: (bind/transform #(% (get-current-instrument-id))) 128: (bind/notify-later) 129: (bind/tee 130: (bind-transfrom-set-model #(-> (:bids %) 131: depth-data-model) root :#bids-table) 132: (bind-transfrom-set-model #(-> (:asks %) 133: depth-data-model) root :#asks-table))) 134: 135: ;; 當前選擇的交易對修改就啓動新的深度信息服務 136: (add-watch coin-pair-data :depth-view (fn [k _ _ new-data] 137: (-> (get-current-instrument-id) 138: run-get-instrument-services!))))) 139: 140: ;;;;;;;;;;;;;;;;;; 如下爲新加的gui加載代碼 141: 142: (defn my-form 143: "加載form" 144: [] 145: (let [form (identify (DepthWindow.))] 146: 147: ;; 更新quote-coin的model 148: (gui/config! (gui/select form [:#base-coin]) :model base-coins) 149: (update-quote-coin-model! form (-> (:base-coin @coin-pair-data) 150: get-quote-coins)) 151: 152: ;; 先綁定事件,再設置默認值 153: (add-behaviors form) 154: (gui/value! form @coin-pair-data) 155: 156: form)) 157: 158: (defn -main [& args] 159: (gui/invoke-later 160: (let [form (my-form)] 161: (-> form gui/pack! gui/show!))))
clojure從java加載代碼仍是很是簡單的,這裏多了一個綁定控件的name到swing id的動做。 shell
3 總結
使用netbeans設計GUI,而後從clojure中加載界面代碼仍是很是方便的。主要是從clojure中調用java很是方便,參考Clojure is a better Java than Java。 編程
整個項目的地址在okex。 json