clojure GUI编程-2

Posted ntestoc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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,文件内容如下:

  1: /*
  2:  * To change this license header, choose License Headers in Project Properties.
  3:  * To change this template file, choose Tools | Templates
  4:  * and open the template in the editor.
  5:  */
  6: package okex;
  7: /**
  8:  *
  9:  */
 10: public class DepthWindow extends javax.swing.JFrame {
 11: 
 12:     /**
 13:      * Creates new form DepthWindow
 14:      */
 15:     public DepthWindow() {
 16:         initComponents();
 17:     }
 18: 
 19:     /**
 20:      * This method is called from within the constructor to initialize the form.
 21:      * WARNING: Do NOT modify this code. The content of this method is always
 22:      * regenerated by the Form Editor.
 23:      */
 24:     @SuppressWarnings("unchecked")
 25:     // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
 26:     private void initComponents() {
 27: 
 28:         jPanel1 = new javax.swing.JPanel();
 29:         jPanel2 = new javax.swing.JPanel();
 30:         jLabel1 = new javax.swing.JLabel();
 31:         jComboBox1 = new javax.swing.JComboBox<>();
 32:         jLabel2 = new javax.swing.JLabel();
 33:         jComboBox2 = new javax.swing.JComboBox<>();
 34:         jScrollPane1 = new javax.swing.JScrollPane();
 35:         jTable1 = new javax.swing.JTable();
 36:         jScrollPane2 = new javax.swing.JScrollPane();
 37:         jTable2 = new javax.swing.JTable();
 38:         jLabel3 = new javax.swing.JLabel();
 39:         jLabel4 = new javax.swing.JLabel();
 40: 
 41:         setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
 42:         setTitle("okex 行情信息");
 43: 
 44:         jPanel1.setLayout(new java.awt.BorderLayout(3, 3));
 45: 
 46:         jLabel1.setText("基准币种:");
 47: 
 48:         jComboBox1.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
 49:         jComboBox1.setName("base-coin"); // NOI18N
 50: 
 51:         jLabel2.setText("计价币种:");
 52: 
 53:         jComboBox2.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
 54:         jComboBox2.setToolTipText("");
 55:         jComboBox2.setName("quote-coin"); // NOI18N
 56: 
 57:         jTable1.setModel(new javax.swing.table.DefaultTableModel(
 58:             new Object [][] {
 59:                 {null, null, null, null},
 60:                 {null, null, null, null},
 61:                 {null, null, null, null},
 62:                 {null, null, null, null}
 63:             },
 64:             new String [] {
 65:                 "Title 1", "Title 2", "Title 3", "Title 4"
 66:             }
 67:         ));
 68:         jTable1.setName("bids-table"); // NOI18N
 69:         jScrollPane1.setViewportView(jTable1);
 70: 
 71:         jTable2.setModel(new javax.swing.table.DefaultTableModel(
 72:             new Object [][] {
 73:                 {null, null, null, null},
 74:                 {null, null, null, null},
 75:                 {null, null, null, null},
 76:                 {null, null, null, null}
 77:             },
 78:             new String [] {
 79:                 "Title 1", "Title 2", "Title 3", "Title 4"
 80:             }
 81:         ));
 82:         jTable2.setName("asks-table"); // NOI18N
 83:         jScrollPane2.setViewportView(jTable2);
 84: 
 85:         jLabel3.setFont(new java.awt.Font("sansserif", 1, 12)); // NOI18N
 86:         jLabel3.setText("买入信息");
 87: 
 88:         jLabel4.setFont(new java.awt.Font("sansserif", 1, 12)); // NOI18N
 89:         jLabel4.setText("卖出信息");
 90: 
 91:         javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
 92:         jPanel2.setLayout(jPanel2Layout);
 93:         jPanel2Layout.setHorizontalGroup(
 94:             jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 95:             .addGroup(jPanel2Layout.createSequentialGroup()
 96:                 .addContainerGap()
 97:                 .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
 98:                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
 99:                 .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
100:                 .addContainerGap())
101:             .addGroup(jPanel2Layout.createSequentialGroup()
102:                 .addGap(18, 18, 18)
103:                 .addComponent(jLabel1)
104:                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
105:                 .addComponent(jComboBox1, javax.swing.GroupLayout.PREFERRED_SIZE, 171, javax.swing.GroupLayout.PREFERRED_SIZE)
106:                 .addGap(125, 125, 125)
107:                 .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 69, javax.swing.GroupLayout.PREFERRED_SIZE)
108:                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
109:                 .addComponent(jComboBox2, javax.swing.GroupLayout.PREFERRED_SIZE, 141, javax.swing.GroupLayout.PREFERRED_SIZE)
110:                 .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
111:             .addGroup(jPanel2Layout.createSequentialGroup()
112:                 .addGap(151, 151, 151)
113:                 .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, 113, javax.swing.GroupLayout.PREFERRED_SIZE)
114:                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
115:                 .addComponent(jLabel4, javax.swing.GroupLayout.PREFERRED_SIZE, 83, javax.swing.GroupLayout.PREFERRED_SIZE)
116:                 .addGap(202, 202, 202))
117:         );
118: 
119:         jPanel2Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {jLabel1, jLabel2});
120: 
121:         jPanel2Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {jComboBox1, jComboBox2});
122: 
123:         jPanel2Layout.setVerticalGroup(
124:             jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
125:             .addGroup(jPanel2Layout.createSequentialGroup()
126:                 .addContainerGap()
127:                 .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
128:                     .addComponent(jLabel1)
129:                     .addComponent(jComboBox1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
130:                     .addComponent(jLabel2)
131:                     .addComponent(jComboBox2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
132:                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
133:                 .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
134:                     .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)
135:                     .addComponent(jLabel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
136:                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
137:                 .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
138:                     .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 284, Short.MAX_VALUE)
139:                     .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
140:                 .addContainerGap())
141:         );
142: 
143:         jComboBox1.getAccessibleContext().setAccessibleName("");
144: 
145:         jPanel1.add(jPanel2, java.awt.BorderLayout.CENTER);
146: 
147:         javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
148:         getContentPane().setLayout(layout);
149:         layout.setHorizontalGroup(
150:             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
151:             .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
152:                 .addContainerGap()
153:                 .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, 468, Short.MAX_VALUE)
154:                 .addContainerGap())
155:         );
156:         layout.setVerticalGroup(
157:             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
158:             .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
159:                 .addContainerGap()
160:                 .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
161:                 .addContainerGap())
162:         );
163: 
164:         pack();
165:     }// </editor-fold>//GEN-END:initComponents
166: 
167:     /**
168:      * @param args the command line arguments
169:      */
170:     public static void main(String args[]) {
171:         /* Set the Nimbus look and feel */
172:         //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
173:         /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
174:          * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
175:          */
176:         try {
177:             for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
178:                 if ("Nimbus".equals(info.getName())) {
179:                     javax.swing.UIManager.setLookAndFeel(info.getClassName());
180:                     break;
181:                 }
182:             }
183:         } catch (ClassNotFoundException ex) {
184:             java.util.logging.Logger.getLogger(DepthWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
185:         } catch (InstantiationException ex) {
186:             java.util.logging.Logger.getLogger(DepthWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
187:         } catch (IllegalAccessException ex) {
188:             java.util.logging.Logger.getLogger(DepthWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
189:         } catch (javax.swing.UnsupportedLookAndFeelException ex) {
190:             java.util.logging.Logger.getLogger(DepthWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
191:         }
192:         //</editor-fold>
193: 
194:         /* Create and display the form */
195:         java.awt.EventQueue.invokeLater(new Runnable() {
196:             public void run() {
197:                 new DepthWindow().setVisible(true);
198:             }
199:         });
200:     }
201: 
202:     // Variables declaration - do not modify//GEN-BEGIN:variables
203:     private javax.swing.JComboBox<String> jComboBox1;
204:     private javax.swing.JComboBox<String> jComboBox2;
205:     private javax.swing.JLabel jLabel1;
206:     private javax.swing.JLabel jLabel2;
207:     private javax.swing.JLabel jLabel3;
208:     private javax.swing.JLabel jLabel4;
209:     private javax.swing.JPanel jPanel1;
210:     private javax.swing.JPanel jPanel2;
211:     private javax.swing.JScrollPane jScrollPane1;
212:     private javax.swing.JScrollPane jScrollPane2;
213:     private javax.swing.JTable jTable1;
214:     private javax.swing.JTable jTable2;
215:     // End of variables declaration//GEN-END:variables
216: }

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

作者: ntestoc

Created: 2019-05-29 周三 22:01

以上是关于clojure GUI编程-2的主要内容,如果未能解决你的问题,请参考以下文章

特价┃Clojure编程(china-pub首发)

Clojure学习笔记——函数式编程

clojure的语法糖

使用底图作为Python GUI中的图形

Clojure:让我兴奋的编程语言

Clojure安装与入门