Hi,
I managed to get myself sorted out with my previous question.
Now I'm having trouble adding a button to my application. I'm not sure whether this is a lisp question, or an ltk question, so please bear with me. Here's a simplified version of the failing code:
(in-package :ltk) (defparameter *cvs* nil)
(defun draw-circle (x y radius color) (format t "B Canvas is ~a~%" *cvs*) (itemconfigure *cvs* (create-oval *cvs* (- x radius) (- y radius) (+ x radius) (+ y radius)) :outline color))
(defun testit () (with-ltk () (let* ((*cvs* (make-instance 'canvas :width 200 :height 200)) (b (make-instance 'button :master nil :text "Do it!" :command (lambda () (draw-circle 100 100 20 :red)))))
(format t "A Canvas is ~a~%" *cvs*) (pack *cvs*) (pack b) (draw-circle 100 100 40 :blue))))
When I load this, and call the function testit from the REPL, it displays the blue circle correctly, but fails to display the red one. Here is what I see in the REPL:
CL-USER> (load "testit.lisp") #P"/Users/neil/devel/lispgui/testit.lisp" CL-USER> (in-package :ltk) #<Package "LTK"> LTK> (testit) A Canvas is #<CANVAS #x873D586> B Canvas is #<CANVAS #x873D586> B Canvas is NIL
It prints the "B Canvas is NIL" when I click on the button, so this is obviously why it fails to draw the red circle. But, I don't understand why *cvs* is nil in this case. I'm sure this is painfully obvious, but not to me. Can someone point me in the right direction please?
Neil.
Hi Neil,
On Tue, Mar 11, 2008 at 5:46 AM, Neil Baylis neil.baylis@gmail.com wrote:
Now I'm having trouble adding a button to my application. I'm not sure whether this is a lisp question, or an ltk question, so please bear with me. Here's a simplified version of the failing code:
It is a lisp question...
(in-package :ltk) (defparameter *cvs* nil)
Here you created the dynamically scoped variable *cvs*, initializing it with "nil" ...
(defun draw-circle (x y radius color) (format t "B Canvas is ~a~%" *cvs*) (itemconfigure *cvs*
which is accessed here...
(create-oval *cvs* (- x radius) (- y radius) (+ x radius) (+ y radius)) :outline color))
(defun testit () (with-ltk () (let* ((*cvs* (make-instance 'canvas :width 200 :height 200))
Here you rebind the variable, setting it to the canvas
(b (make-instance 'button :master nil :text "Do it!" :command (lambda () (draw-circle 100 100 20 :red)))))
This draw-circle is called when the button is pressed, that is *outside* the dynamic contour of the let, and thats why you get the nil.
(format t "A Canvas is ~a~%" *cvs*) (pack *cvs*) (pack b) (draw-circle 100 100 40 :blue))))
This draw-circle is in the dynamic contour of the let, so it succeeds..
When I load this, and call the function testit from the REPL, it displays the blue circle correctly, but fails to display the red one. Here is what I see in the REPL:
CL-USER> (load "testit.lisp") #P"/Users/neil/devel/lispgui/testit.lisp" CL-USER> (in-package :ltk) #<Package "LTK"> LTK> (testit) A Canvas is #<CANVAS #x873D586> B Canvas is #<CANVAS #x873D586> B Canvas is NIL
It prints the "B Canvas is NIL" when I click on the button, so this is obviously why it fails to draw the red circle. But, I don't understand why *cvs* is nil in this case. I'm sure this is painfully obvious, but not to me. Can someone point me in the right direction please?
It is quite annoying, but the event handlers are run in a different dynamic context as where they are bound. A help would be to pass the canvas as a parameter to the event handler like:
(defun draw-circle (canvas x y radius color) ...)
and then set up the button like:
(b (make-instance 'button :master nil :text "Do it!" :command (lambda () (draw-circle *cvs* 100 100 20 :red)))))
As the lambda is a proper closure, it properly catches the value of *cvs* and can pass it on to draw-circle.
HTH, Peter
Thanks Peter, that makes sense. I guess I didn't understand dynamic scope as well as I had thought.
I changed the code inside the (let* ...) as follows, and this works:
(defun testit () (with-ltk () (let* ((canv (setf *cvs* (make-instance 'canvas :width 200 :height 200))) (b (make-instance 'button :master nil :text "Do it!" :command (lambda () (draw-circle 100 100 20 :red)))))
(format t "A Canvas is ~a~%" *cvs*) (pack canv) (pack b) (draw-circle 100 100 40 :blue))))
This makes it more obvious (to me..) that the canvas bound inside the (let*...) is different from the global one. I guess this is how the ltk::ltktest example is able to work, e.g. the start and stop buttons.
I bet this wont be the last time I am bitten by that problem.
Thanks,
Neil