强制Clojure宏中的参数获取命名空间

时间:2011-06-30 20:34:30

标签: macros clojure quoting

我正在开发一个Clojure宏来帮助构建基于GridBagLayout的JPanel。我可以在宏内部的默认映射中将Java类获取到namespace-qualify,但不能将这些类作为参数传入。我需要什么神奇的反引号,引号,tildas或其他东西组合?

(import [java.awt GridBagConstraints GridBagLayout Insets]
        [javax.swing JButton JPanel])

(defmacro make-constraints [gridx gridy & constraints]
  (let [defaults
        {:gridwidth 1 :gridheight 1 :weightx 0 :weighty 0
         :anchor 'GridBagConstraints/WEST :fill 'GridBagConstraints/NONE
         :insets `(Insets. 5 5 5 5) :ipadx 0 :ipady 0}

        values
        (assoc (merge defaults (apply hash-map constraints))
          :gridx gridx :gridy gridy)]
    `(GridBagConstraints. ~@(map (fn [value]
                                   (if
                                    (or
                                     (number? value)
                                     (string? value)
                                     (char? value)
                                     (true? value)
                                     (false? value)
                                     (nil? value))
                                    value
                                    `~value))
                                 (map values
                                      [:gridx :gridy :gridwidth :gridheight
                                       :weightx :weighty :anchor :fill
                                       :insets :ipadx :ipady])))))

当我使用默认地图中定义的Insets时,它会被(java.awt.Insets ...)限定(不是“符号捕获”):

user=> (macroexpand-1 '(make-constraints 0 0 :weightx 1))
(java.awt.GridBagConstraints.
 0 0 1 1 1 0
 GridBagConstraints/WEST GridBagConstraints/NONE
 (java.awt.Insets. 5 5 5 5) 0 0)

但是当我把它作为参数传递时,它不会:

user=> (macroexpand-1 '(make-constraints 1 1 :insets (Insets. 2 2 2 2)))
(java.awt.GridBagConstraints.
 1 1 1 1 0 0
 GridBagConstraints/WEST GridBagConstraints/NONE
 (Insets. 2 2 2 2) 0 0)

我不只是想成为一个坚持者。我收到编译器错误,找不到合适的GridBagConstraints构造函数。

2 个答案:

答案 0 :(得分:3)

我不知道GridBagLayout,但以下应基本上与您的宏类似。如果您的组件:height大于1,则必须在其下方的列中添加nil以使列计数器保持同步。比如,您的arrive-text-field的高度为2,而不是必须在nil行之前添加一行depart-label,以保持计数器的正确性。这只是一个快速的黑客攻击。

(def default-opts
  {:insets   (Insets. 0 0 0 0)
   :width    1
   :height   1
   :weight-x 0.0
   :weight-y 0.0
   :fill     GridBagConstraints/NONE
   :anchor   GridBagConstraints/WEST
   :ipadx    0
   :ipady    0})

(defn grid-bag-constraints
  [x y global-opts opts]
  (let [{:keys [insets width height weight-x weight-h
                fill anchor ipadx ipady]}
        (merge default-opts global-opts opts)]
    (GridBagConstraints. x y width height weight-x weight-h
                         anchor fill insets ipadx ipady)))

(defn grid-bag-container
  [panel global-opts & rows]
  (doseq [[row-idx row] (map-indexed identity rows)
          [col-idx [target & {:as opts}]] (map-indexed identity row)
          :when target]
    (let [constraints (grid-bag-constraints col-idx row-idx global-opts opts)]
      (.add panel target constraints))))

与以前一样使用。

答案 1 :(得分:1)

这是我的解决方案。我在我正在编写的Swing应用程序中使用它。它已经为我节省了许多代码行(用于两个不同的面板),并且与手写代码一样快。

(defmacro grid-bag-container [container & args]
  "Fill and return a java.awt.Container that uses the GridBagLayout.
  The macro defines a set of default constraints for the GridBagConstraints:
    :gridwidth 1
    :gridheight 1
    :weightx 0
    :weighty 0
    :anchor :WEST
    :fill :NONE
    :insets (Insets. 5 5 5 5)
    :ipadx 0
    :ipady 0
  These defaults can be overridden in the call to the macro in two way:
    - If the first argument is a hash-map of constraint names and values
      (e.g.: {:weightx 1}), these will override the defaults for the
      entire container.
    - Each individual item (see below) can override the global defaults
      and container defaults for itself.
  The constraints consist of constraint name (as a keyword with the same
  name as the GridBagConstraints field), and a value, which can also be
  a keyword, in which case the appropriate constant from GridBagConstraints
  will be substituted (e.g.: :NONE == GridBagConstraints.NONE), or the value
  can be an expression (e.g.: 0 or (Insets. 2 2 2 2)).
  Following the optional container default overrides hash-map are one or
  more row specification vectors. Each vector represents one row and
  increments gridy (starting from 0). Each vector contains one or more
  item vectors representing the individual components to be added to the
  container. Each item vector has the component as its first value,
  followed by zero or more constraint overrides as keyword-value pairs.
  (e.g.: [myButton :gridwidth 2 :weightx 1]). The values may be keywords
  and are expanded to GridBagConstraints constants as described above.
  Each item vector gets the next value of gridx (starting with 0) in that
  row.
  For example:
    (grid-bag-container panel
      {:insets (Insets. 1 1 1 1)}
      [[button :gridwidth 2 :weightx 1.0 :fill :HORIZONTAL]]
      [[check-box :gridwidth 2 :weightx 1.0 :anchor :CENTER]]
      [[arrive-label] [arrive-text-field :fill :HORIZONTAL]]
      [[depart-label] [depart-text-field :fill :HORIZONTAL]])
  will expand to the hand-written equivalent:
    (doto panel
      (.add button
        (GridBagConstraints. 0 0 2 1 1.0 0  ; gridx: 0 gridy: 1
                             GridBagConstraints/WEST
                             GridBagConstraints/HORIZONTAL
                             (Insets. 1 1 1 1) 0 0))
      (.add check-box
        (GridBagConstraints. 0 1 2 1 1.0 0  ; gridx: 0 gridy: 1
                             GridBagConstraints/CENTER
                             GridBagConstraints/NONE
                             (Insets. 1 1 1 1) 0 0))
      (.add arrive-label
        (GridBagConstraints. 0 2 1 1 0 0    ; gridx: 0 gridy: 2
                             GridBagConstraints/WEST
                             GridBagConstraints/NONE
                             (Insets. 1 1 1 1) 0 0))
      (.add arrive-text-field
        (GridBagConstraints. 1 2 1 1 0 0    ; gridx: 1 gridy: 2
                             GridBagConstraints/WEST
                             GridBagConstraints/HORIZONTAL
                             (Insets. 1 1 1 1) 0 0))
      (.add depart-label
        (GridBagConstraints. 0 3 1 1 0 0    ; gridx: 0 gridy: 3
                             GridBagConstraints/WEST
                             GridBagConstraints/NONE
                             (Insets. 1 1 1 1) 0 0))
      (.add depart-text-field
        (GridBagConstraints. 1 3 1 1 0 0    ; gridx: 1 gridy: 3
                             GridBagConstraints/WEST
                             GridBagConstraints/HORIZONTAL
                             (Insets. 1 1 1 1) 0 0))
  @param container the java.awt.Container to fill
  @param args the components and GridBagContraints speicifcations
  @returns the filled Container"
  (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 rows]
        (if (map? (first args))
          [(into global-defaults (first args)) (rest args)]
          [global-defaults args])]
    `(doto ~container
      ~@(loop [gridy 0 rows rows ret []]
        (if (seq rows)
          (recur (inc gridy) (rest rows)
            (into ret
              (let [row (first rows)]
                (loop [gridx 0 row row ret []]
                  (if (seq row)
                    (recur (inc gridx) (rest row)
                      (conj ret
                        (let [item
                              (first row)

                              component
                              (first item)

                              constraints
                              (assoc (merge defaults
                                            (apply hash-map (rest item)))
                                :gridx gridx :gridy gridy)

                              constraint-values
                              (map (fn [value]
                                (if (keyword? value)
                                  `(. GridBagConstraints
                                      ~(symbol (name value)))
                                  `~value))
                                (map constraints
                                  [:gridx :gridy :gridwidth :gridheight
                                   :weightx :weighty :anchor :fill
                                   :insets :ipadx :ipady]))]
                          `(.add ~component (new GridBagConstraints
                                                 ~@constraint-values)))))
                    ret)))))
          ret)))))

感谢amalloyuser100464kotarak寻求帮助。