clojure GUI编程-2
1 简介
接上一篇GUI开发,每次手写GUI布局代码比较不方便,可以使用netbeans的form designer设计好界面,然后从clojure中加载界面,绑定事件来进行GUI设计。
2 实现过程
由于要编译java代码,使用leiningen进行项目管理比较方便。先创建一个空项目, lein new okex 创建项目。
2.1 添加依赖
修改项目文件夹下的project.clj如下
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。 并修改命名空间与文件名对应。
2.3 设计gui界面
使用netbeans新建JFrame form,并设计窗体,修改要用到的widget的name属性为对应的swing id名。
然后保存这个文件到src/okex文件夹下,注意包名要用okex。窗体设计器自动生成的DepthWindow.java。
2.4 clojure中加载java gui代码
修改core.clj,导入gui界面的类,并加载,代码如下:
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的动作。
3 总结
使用netbeans设计GUI,然后从clojure中加载界面代码还是非常方便的。主要是从clojure中调用java非常方便,参考Clojure is a better Java than Java。
整个项目的地址在okex。
Created: 2019-05-31 周五 17:40