clojure GUI编程-2

clojure GUI编程-2

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

作者: ntestoc

Created: 2019-05-31 周五 17:40

posted @ 2019-05-29 21:53  cloca  阅读(322)  评论(0编辑  收藏  举报