Frage

I'm binding an instance to a Var:

(ns org.jb
  (:import (java.awt PopupMenu
                     TrayIcon
                     Toolkit
                     SystemTray)

           (javax.swing JFrame
                        Action)))

(def ^:dynamic popupmenu)
(def ^:dynamic image)
(def ^:dynamic trayicon)
(def ^:dynamic tray)

(defn start-app [appname icon]
  (binding [popupmenu (new PopupMenu)
            image (.. Toolkit (getDefaultToolkit) (getImage icon))
            trayicon (new TrayIcon image appname popupmenu)
            tray (. SystemTray getSystemTray)]

    (. trayicon setImageAutoSize true)    

    (. tray add trayicon)))

(start-app "escap" "res/escap_icon.png")

Error:

ClassCastException clojure.lang.Var$Unbound cannot be cast to java.awt.Image  org.jb/start-app (org\jb.clj:17)

I am predefining the Var with

(def image)

even tried

(def ^:dynamic image)

Unable to understand what is expected from the message.

Using let in place of binding works within the lexical scope however. However want to achieve dynamic binding.

War es hilfreich?

Lösung

All I see here is an empty binding form with no code. The variable binding goes out of scope once you leave the binding form. Based on your error message, it looks like you're trying to use the image var outside of the binding form. You need to make sure that all code that uses image is placed inside the binding.

So, instead of this:

(binding [*image* (.. Toolkit (getDefaultToolkit) (getImage "icon.png"))])
(display-image *image*)

Do this:

(binding [*image* (.. Toolkit (getDefaultToolkit) (getImage "icon.png"))]
  (display-image *image*))

Another possible issue is that the binding expressions are evaluated in parallel whereas expressions in let are evaluated in sequence. This means that if you're binding multiple vars and one depends on the other, it will use the value that was in scope before the binding was evaluated.

So, this will throw an exception:

(def ^:dynamic *a*)
(def ^:dynamic *b*)
(binding [*a* 2
          *b* (+ *a* 3)]
  (+ *a* *b*)) ; => ClassCastException clojure.lang.Var$Unbound cannot be cast
               ; to java.lang.Number  clojure.lang.Numbers.multiply
               ; (Numbers.java:146)

Instead you would have to use nested binding forms:

(binding [*a* 2]
  (binding [*b* (+ *a* 3)]
    (+ *a* *b*))) ; => 8

Note that I've placed "earmuffs" around the var name. This is the naming convention for dynamic vars in Clojure, so other people can easily tell that it's dynamic. Also, if you're able to dynamically bind a var without declaring it with ^:dynamic metadata, that means you're using a pretty old version of Clojure. I'd suggest you upgrade - 1.5.1 is the latest stable release.

Andere Tipps

There is no point using binding in your example. You should use binding only when you want to rebind global variable to create some context. In your case you don't need global variables, so you should use let instead:

(ns org.jb
  (:import (java.awt PopupMenu
                     TrayIcon
                     Toolkit
                     SystemTray)
           (javax.swing JFrame
                        Action)))

(defn start-app [appname icon]
  (let [popupmenu (new PopupMenu)
        image (.. Toolkit (getDefaultToolkit) (getImage icon))
        trayicon (new TrayIcon image appname popupmenu)
        tray (. SystemTray getSystemTray)]
    (. trayicon setImageAutoSize true)    
    (. tray add trayicon)))

(start-app "escap" "res/escap_icon.png")

But if you'll decide to stick with binding then Alex's answer shuld help you with your problem.

But you should avoid using things like binding unless they are absolutely necessary.

Update

If your goal is to save some state for future calculations then binding won't be able to help you. It only binds variables with new values inside its body, leaving them intact for the rest of your app.

So, when you want to change a global state you should use alter-var-root instead:

(def ^:dynamic *app-state* {})

(defn set-state! [new-state]
  (alter-var-root #'*app-state* (constantly new-state)))

(defn update-state! [mixin]
  (alter-var-root #'*app-state* merge mixin))

You should also try to keep the most of your functions as close to "functional paradigm" as you can:

(defn start-app
  "Creates new app with given appname and icon and returns it"
  [appname icon]
  (let [popupmenu (new PopupMenu)
        image (.. Toolkit (getDefaultToolkit) (getImage icon))
        trayicon (new TrayIcon image appname popupmenu)
        tray (. SystemTray getSystemTray)]
    (. trayicon setImageAutoSize true)    
    (. tray add trayicon)
    { :popupmenu popupmenu
      :image image
      :trayicon trayicon
      :tray }))

(update-state! (start-app "escap" "res/escap_icon.png"))
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top