我正在編寫一個Clojure宏,它接受使用java.awt.GridBagLayout
的面板描述並在編譯時生成等效代碼(使用(doto ...)
)。我知道seesaw,但我正在努力學習宏觀寫作的細節。Clojure GridBag宏和print-dup
我的問題:
- 在什麼時候在編譯期間是Java方法(如
(Insets. 5 5 5 5)
)編譯(生成的字節碼)? - 爲什麼從宏返回這些問題?
- 編譯器不應該「看到」並編譯相同的東西,就像我手動擴展宏一樣?
- 是否有任何可以從幫助程序函數返回的內容,可以改善這種情況,可能使用
(eval ...)
或#=(...)
,因爲沒有相關的運行時間損失?
我知道這可以寫成(defn ...)
和(很容易)解決問題。我想用宏實現相同的結果,因爲我可以看到其他情況下,運行時性能損失可能不可接受(在這種情況下爲而不是,因爲這是GUI代碼)。我編寫這個宏的原因是因爲我相信結果比手動擴展版本更容易閱讀和維護。
我已經包含了兩個(print-dup...)
多方法的定義,以努力滿足編譯器並消除(不成功)運行時錯誤消息「無法在代碼中嵌入對象,也許未定義print-dup:java.awt。插圖[頂= 5,左= 5,底部= 5,右側= 5]」
的宏被調用這樣的:
(grid-bag-container (JPanel. (GridBagLayout.))
[(JButton "Monday") :gridwidth 2 :weightx 1.0 :fill :HORIZONTAL]
[(JCheckBox "Vacation")]
[[(JLabel. "Arrive:")] [(JTextField. 6) :fill :HORIZONTAL]]
[[(JLabel. "Depart:")] [(JTextField. 6) :fill :HORIZONTAL]])
這裏是預期的膨脹(適合打印易於可讀性: - )):
(doto (JPanel. (GridBagLayout.))
(.add (JButton "Monday")
(GridBagConstraints. 0 0 2 1 1.0 0
(. GridBagConstraints WEST)
(. GridBagConstraints HORIZONTAL)
(Insets. 2 2 2 2) 0 0))
(.add (JCheckBox "Vacation")
(GridBagConstraints. 0 1 1 1 0 0
(. GridBagConstraints WEST)
(. GridBagConstraints NONE)
(Insets. 2 2 2 2) 0 0))
(.add (JLabel. "Arrive:")
(GridBagConstraints. 0 2 1 1 0 0
(. GridBagConstraints WEST)
(. GridBagConstraints NONE)
(Insets. 2 2 2 2) 0 0))
(.add (JTextField. 6)
(GridBagConstraints. 1 2 1 1 0 0
(. GridBagConstraints WEST)
(. GridBagConstraints HORIZONTAL)
(Insets. 2 2 2 2) 0 0))
(.add (JLabel. "Depart:")
(GridBagConstraints. 0 3 1 1 0 0
(. GridBagConstraints WEST)
(. GridBagConstraints NONE)
(Insets. 2 2 2 2) 0 0))
(.add (JTextField. 6)
(GridBagConstraints. 1 3 1 1 0 0
(. GridBagConstraints WEST)
(. GridBagConstraints HORIZONTAL)
(Insets. 2 2 2 2) 0 0)))
下面是代碼:
(defmethod print-dup java.awt.GridBagConstraints [args writer]
"A multimethod for converting java.awt.GridBagConstraints to a compiled form.
@param args a collection of constructor arguments
@param writer the Writer to which the output should be generated"
(.write writer "#=(java.awt.GridBagConstraints. ")
(.write writer (apply str (interpose " " (map str args))))
(.write writer ")"))
(defmethod print-dup java.awt.Insets [args writer]
"A multimethod for converting java.awt.Insets to a compiled form.
@param args a collection of (Integer) constructor arguments
@param writer the Writer to which the output should be generated"
(.write writer "#=(java.awt.Insets. ")
(.write writer (apply str (interpose " " (map str args))))
(.write writer ")"))
(defmacro grid-bag-container [container & args]
"Fill a container having a GridBagLayout with the given components.
The args can start with an optional default-constraints map (see the
doc-string for build-gbc (below) for details on the constraints map).
Following the optional default-constraints are zero or more rows.
Each row is a vector containing either a single component specification
or multiple vectors of component specifications. Each component specification
is a component (e.g.: JButton) followed by one or more key-value constraints
of the same form as the default-constraints. Note that these key-value
pairs are NOT contained in a map. Each row vector will be placed in
the next gridy position (starting with 0). If a row vector contains only
one component specification, that component will be placed at gridx=0.
If a row vector contains vectors, each will be placed at the next gridx
position (starting with 0). The default values for the constraints are as
follows:
:gridwidth 1
:gridheight 1
:weightx 0
:weighty 0
:anchor :WEST
:fill :NONE
:insets (Insets. 5 5 5 5)
:ipadx 0
:ipady 0
For example:
(grid-bag-container
(JPanel.)
{:insets (Insets. 2 2 2 2)} ; Override the default (Insets. 5 5 5 5)
[button :gridwidth 2 :weightx 1] ; Add a button at (gridx=0, gridy=0) with the
; gridwidth=2 (overriding the default 1),
; and weightx=1 (overriding the default 0)
[[label] [textfield :fill :HORIZONTAL]]) ; Add a label at (gridx=0, gridy=1)
; and a textfield at (gridx=1, gridy=1),
; with fill=GridBagContraints.CENTER
; (overriding the default GridBagContraints.WEST)
This example will expand to
(doto container
(.add button (build-gbc {:gridx 0 :gridwidth 2 :ipadx 0 :ipady 0 :anchor :WEST :weighty 0
:gridheight 1 :weightx 1 :fill :NONE :insets (Insets. 2 2 2 2)
:gridy 0}))
(.add label (build-gbc {:gridx 0 :gridwidth 1 :ipadx 0 :ipady 0 :anchor :WEST :weighty 0
:gridheight 1 :weightx 0 :fill :NONE :insets (Insets. 2 2 2 2)
:gridy 1}))
(.add textfield (build-gbc {:gridx 1 :gridwidth 1 :ipadx 0 :ipady 0 :anchor :WEST :weighty 0
:gridheight 1 :weightx 0 :fill :HORIZONTAL :insets (Insets. 2 2 2 2)
:gridy 1})))
@param container the java.awt.Container to fill
@args an optional default-constraints map followed by zero or more row specifications
@returns the container
build-gbc:
Build and return a GridBagConstraints containing the given constraints map.
Each constraint is a (:key value) pair where the name of the key is a
GridBagConstraints field (e.g.: gridwidth) and the value is either a keyword
(e.g.: :CENTER), in which case the GridBagConstraints constant of the same name
(e.g.: GridBagConstraints.CENTER) is used, or anything else, in which case the
corresponding field is set to that value.
Example:
(build-gbc {:gridx 0
:gridy 0
:gridheight 1
:gridwidth 2
:weightx 1
:weighty 0
:anchor :CENTER
:fill :NONE
:insets (Insets. 2 2 2 2)
:ipadx 0
:ipady 0})
will build and return a GridBagConstraints containing the following field values:
gridx 0
gridy 0
gridheight 1
gridwidth 2
weightx 1
weighty 0
anchor GridBagConstraints.CENTER
fill GridBagConstraints.NONE
insets (Insets. 2 2 2 2)
ipadx 0
ipady 0.
@param constraints a map containing the GridBagConstraints constraint values
@returns a new GridBagConstraints
@see http://stuartsierra.com/2010/01/05/taming-the-gridbaglayout"
(let [global-defaults {:gridwidth 1
:gridheight 1
:weightx 0
:weighty 0
:anchor :WEST
:fill :NONE
:insets (Insets. 5 5 5 5)
:ipadx 0
:ipady 0}
defaults
(if (map? (first args))
(first args)
{})
args
(into []
(if (map? (first args))
(rest args)
args))
build-gbc
(fn [constraints]
(let [process-value
#(if (nil? %)
nil
(if (keyword? %)
`(. GridBagConstraints ~(symbol (name %)))
%))]
`(GridBagConstraints.
~(process-value (:gridx constraints))
~(process-value (:gridy constraints))
~(process-value (:gridwidth constraints))
~(process-value (:gridheight constraints))
~(process-value (:weightx constraints))
~(process-value (:weighty constraints))
~(process-value (:anchor constraints))
~(process-value (:fill constraints))
~(process-value (:insets constraints))
~(process-value (:ipadx constraints))
~(process-value (:ipady constraints)))))]
`(doto ~container
[email protected](loop [end (count args)
gridy 0
ret []]
(if (= end gridy)
ret
(let [row (nth args gridy)
process-item
(fn [component gridx gridy constraints]
(let [constraints
(reduce into global-defaults
[{:gridx gridx :gridy gridy}
defaults
(vec (map vec (partition 2 constraints)))])]
`(.add ~component ~(build-gbc constraints))))]
(if (vector? (first row))
(recur end
(inc gridy)
(into ret (for [gridx (range (count row))
:let [item (nth row gridx)
component (first item)
constraints (rest item)]]
(process-item component gridx gridy constraints))))
(recur end
(inc gridy)
(conj ret (let [component (first row)
constraints (rest row)]
(process-item component 0 gridy constraints)))))))))))
謝謝!我要去嘗試一下。我的宏還支持一個可選的初始參數,其中包含覆蓋全局默認值:'(grid-bag-container panel {:insets(Insets 2 2 2 2)} ...)''。你知道這些也應該引用嗎? (我是這麼認爲的)。 – Ralph
是否需要引用宏的參數取決於宏的實現;在這種情況下,您可能想要接受未加引號(Inset ..)的代碼。你必須記住的是宏的輸出是代碼(沒有實例化的GUI對象),宏的參數也是(未評估的)代碼。 –
您的示例調用還包括(JButton「blah」)而不是(JButton。「blah」),但我認爲@Joost的真正問題在控制之下。 – amalloy