views:

72

answers:

2

I'm using "proxy" to extend various Swing classes in a Clojure GUI application, generally with code that looks something like:

(def ^JPanel mypanel 
  (proxy [JPanel] []
    (paintComponent [#^Graphics g]
      (.drawImage g background-image 0 0 nil))))

This works well but I can't figure out how to add additional fields to the newly extended class, for example making the background-image a field that could be subsequently updated. This would be pretty easy and common practice in Java.

Is there a good way to do this in Clojure? Or is there another preferred method to achieve the same effect?

+5  A: 

You can use something like this:

(defn ^JPanel mypanel [state]
  (proxy [JPanel] []
    (paintComponent [#^Graphics g]
      (do
        (comment do something with state)
        (.drawImage g background-image 0 0 nil)))))

or use any other outer function/ref.

splix
very interesting idea - do you mean with defn rather than def?
mikera
doto seems appropriate here: (doto g (.drawImage...) (...))
Greg Harman
hmmmm... anyone know how Clojure is implementing this internally? is it actually adding a field to the proxied class?
mikera
It is `defn` not `def` -- with `def` it wouldn't even compile -- I took the liberty of making the correction. As for the implementation, there is no `proxy`-specific magic here; method bodies are closures and can refer to whatever is visible in their lexical scope. BTW, method bodies also have an implicit `do`, so the `do` above is unnecessary.
Michał Marczyk
Incidentally, I really like both the question and this answer. :-)
Michał Marczyk
@mikera: no. proxy methods are ye olde clojure functions and hence close over their environment. So the reference to the state is saved in the closure.
kotarak
hmmm so if I understand correctly is extending the JPanel with a paintComponent method that immediately calls an IFn that captures state as an internal private field?
mikera
@Michal - I agree, very enlightening!
mikera
yes. that's roughly what happens. There is a map lookup somewhere in between to find the actual clojure function to call, but then things go as you described. (Though I wouldn't describe closed-over values as "internal private field") And it should read "captured" not "captures". The "capture" of the state happens when the proxy is created, not when the function is called.
kotarak
+1  A: 

Shameless self-promotion: I wrote a blog post a while ago about proxy and its gotchas: http://bit.ly/cS8AjJ

kotarak
thanks - very useful guide!
mikera