2 Window Procedures and Classes
dxtr edited this page 2026-10-18 15:34:41 +02:00

Window Procedures and Classes

Defining a window procedure

(win32:define-window-procedure my-procedure (window message wparam lparam)
  "Count clicks, and close on Escape."
  (cond ((= message win32:+wm-lbuttondown+)
         (incf *clicks*)
         0)
        ((and (= message win32:+wm-keydown+)
              (eq (win32:virtual-key-name wparam) :escape))
         (win32:destroy-window window)
         0)
        (t nil)))

define-window-procedure defines two things under the name:

  • a Lisp function of the four arguments, with the body you wrote; and
  • a foreign callback, which Windows calls and which calls the function.

The four arguments are integers: the window's handle, the message, and the message's wparam and lparam. The body may start with a documentation string and declarations.

Because the callback calls the function by name, redefining the procedure, for example from the REPL, takes effect at the next message, for windows that already exist.

What a procedure returns

The body returns Windows gets
an integer that integer, the message's result
nil the result of default-window-procedure, Windows' default handling
anything else the default handling, and a type-error is signalled afterwards

Return nil for every message you do not handle: the default handling is what makes a window behave like a window. Some messages want a particular result; each message's documentation in Windows says which. A few common ones:

Message Return
+wm-close+ 0 if you handled it; nil destroys the window
+wm-erasebkgnd+ 1 if you draw the whole window yourself, so Windows does not paint over it
+wm-xbuttondown+, +wm-xbuttonup+ 1
+wm-paint+ 0 after validate-window, if something else draws the window

Errors and non-local exits

The body runs inside Windows' code. It must not leave by a non-local exit: no throw, return-from or go to outside the body, and no invoking a restart established outside it. An error is the exception: the procedure catches it, gives the message the default handling, and the error is signalled again from the cl-win32 call during which Windows called the procedure. See Errors.

Registering a class

(win32:register-window-class "my-app" 'my-procedure)          ; => an atom, an integer
(win32:register-window-class "my-tool" 'tool-procedure
                             :styles '(:double-clicks) :cursor nil)

register-window-class takes the class's name, a string, and the name of a window procedure, a symbol. Its keys:

  • :styles: class styles, of the type class-style. :double-clicks has the class's windows get +wm-lbuttondblclk+ and its kin; :own-dc gives each window a device context of its own; :horizontal-redraw and :vertical-redraw repaint the whole window when its width or height changes; :no-close disables Close in the window menu.
  • :cursor: :arrow, the default, shows the standard arrow over the class's windows. nil leaves the cursor to the window procedure, which sets it when it gets +wm-setcursor+.

A class name is registered once per program. Registering it again signals a win32-error (error 1410, "Class already exists"). Unregister it with unregister-window-class once all its windows are destroyed.

Window classes belong to the program, not to a thread: windows of one class may be created on several threads.