4 Windows
dxtr edited this page 2026-10-18 15:34:41 +02:00

Windows

Creating and destroying

(win32:create-window "my-app" :title "Editor")                       ; a top-level window, hidden
(win32:create-window "my-app" :styles '(:overlapped-window :visible)) ; shown at once
(win32:create-window "my-app" :parent :message :styles '())          ; message-only

create-window takes a registered class name and returns the new window's handle. Its keys:

  • :title: the window's text, shown in its title bar.
  • :styles: window styles, of the type window-style. The default, :overlapped-window, is a top-level window with a title bar, a window menu, borders that size it, and minimise and maximise buttons. :visible shows it at once. :popup, :child, :caption, :border, :system-menu, :thick-frame, :minimize-box, :maximize-box, :clip-children, :clip-siblings, :disabled, :minimize, :maximize and :overlapped are the parts. Styles combine as their bits do, so overlapping ones, such as :overlapped-window and :caption, may be given together.
  • :extended-styles: extended styles, of the type extended-window-style: :topmost, :tool-window, :app-window, :no-activate, :no-redirection-bitmap.
  • :x, :y, :width, :height: the window's place and size on the screen, title bar and borders included, in pixels; :default, the default, lets Windows choose.
  • :parent: a parent window's handle; for a :popup window, its owner, which it stays above and goes with; :message for a message-only window, which is never shown and only receives messages, a convenient target for posting work to a thread; or nil.

The window belongs to the calling thread, which must run its message loop. Its window procedure already receives messages during create-window, starting with +wm-nccreate+ and +wm-create+.

destroy-window destroys a window. Call it on the window's own thread; from another, Windows refuses (error 5, "Access is denied"). The window procedure receives +wm-destroy+, then +wm-ncdestroy+, before it returns. Destroying a window also destroys the windows it is the parent of.

Popups

A :popup window has no frame. Made with another window as its :parent, it is owned by it: it stays above its owner and is destroyed with it, as a menu or a tooltip is. With the :extended-styles :no-activate and :tool-window, it is not activated when clicked and has no button on the taskbar:

(win32:create-window "my-menu" :styles '(:popup)
                               :extended-styles '(:no-activate :tool-window :topmost)
                               :parent owner :x 200 :y 120 :width 160 :height 240)

Its window procedure answers +wm-mouseactivate+ with +ma-noactivate+ to stay inactive when clicked, and +wm-nchittest+ with +httransparent+ to let the pointer through to the window below, as a tooltip does. To keep a menu on the screen, place it within display-work-area; see Displays.

window-p says whether a handle is a window that exists, and window-thread-id which thread a window belongs to, as current-thread-id gives it.

Showing

(win32:show-window window)                    ; :show
(win32:show-window window :show-no-activate)  ; show without taking the keyboard
(win32:show-window window :hide)              ; => T: it was visible

show-window returns whether the window was visible before. Its commands: :show, :show-normal, :show-no-activate, :show-minimized, :hide, :minimize, :maximize, :restore.

Enabling and disabling

(setf (win32:window-enabled-p owner) nil)  ; while a modal dialog is open
(setf (win32:window-enabled-p owner) t)    ; just before it is destroyed

A disabled window gets no input from the mouse and keyboard, and Windows does not activate it: clicking it brings its modal dialog forward instead. Windows' own dialog boxes disable their owner so. Enable the owner again before destroying the dialog, so that Windows activates the owner in the dialog's place. window-enabled-p tells whether a window is enabled.

Sizes and places

Sizes and positions are in pixels, with per-monitor DPI awareness. See DPI.

(win32:client-size window)          ; => 640, 400: the inside
(win32:window-bounds window)        ; => 100, 80, 656, 439: left, top, width, height
(win32:set-window-bounds window :x 0 :y 0)               ; move
(win32:set-window-bounds window :width 800 :height 600)  ; size
(win32:window-size-for-client-size 640 400 :dpi 144)     ; => the size with the frame added
  • client-size is the client area: the inside of the window, where a program draws.
  • window-bounds is the whole window on the screen, title bar and borders included.
  • set-window-bounds moves and sizes the whole window. Give :x and :y together, and :width and :height together; what you leave out stays. It neither activates the window nor changes which windows are above it.
  • window-size-for-client-size adds the title bar and borders of a window with the given :styles and :extended-styles on a display of :dpi to a client size. Use it to size a window by what it draws.

Sizing a window sends it +wm-size+ before set-window-bounds returns.

client-to-screen and screen-to-client convert a position between a window's client area and the screen.

Changing styles

(win32:change-window-styles window :remove '(:overlapped-window :maximize))  ; no frame
(win32:change-window-styles window :add '(:overlapped-window))              ; the frame back

change-window-styles adds and removes window styles, keeping the rest, and has Windows draw the frame anew. The window keeps its bounds, so its client area changes size, which +wm-size+ tells.

Placement

(win32:window-placement window)      ; left, top, width, height, maximized-p, minimized-p
(win32:set-window-placement window 100 80 656 439 :maximized t)

window-placement returns where the window goes when it is neither minimised nor maximised, as its left, top, width and height, then whether it is maximised (or is to be when restored, if it is minimised), and whether it is minimised. set-window-placement puts it back so, and shows it. The position is in workspace coordinates, which are the screen's except where the taskbar is at the top or left of the primary display: give it back to set-window-placement as it came.

Displays

(win32:display-bounds window)               ; left, top, width, height of all of its display
(win32:display-work-area 200 120 160 240)   ; the same, without the taskbar, for a rectangle
  • display-bounds returns all of the display that has the largest part of a window, or the display nearest it, taskbar included: what a fullscreen window covers.
  • display-work-area returns the part of a display that windows may cover, without the taskbar, for the display that has the largest part of a rectangle on the screen. Keep menus and popups within it.

Fullscreen

A fullscreen window has no frame and covers its display. Keep its placement to go back:

(defun enter-fullscreen (window)
  "Make WINDOW cover its display, and return its placement before."
  (let ((placement (multiple-value-list (win32:window-placement window))))
    (win32:change-window-styles window :remove '(:overlapped-window :maximize))
    (multiple-value-bind (x y width height) (win32:display-bounds window)
      (win32:set-window-bounds window :x x :y y :width width :height height))
    placement))

(defun leave-fullscreen (window placement)
  "Give WINDOW its frame back, and put it where PLACEMENT says."
  (win32:change-window-styles window :add '(:overlapped-window))
  (destructuring-bind (x y width height maximized minimized) placement
    (win32:set-window-placement window x y width height
                                :maximized maximized :minimized minimized)))

Text

(win32:window-text window)                         ; => "Editor"
(setf (win32:window-text window) "Editor — notes.txt")

The text of a top-level window is its title. Any Unicode string works; it goes to Windows as UTF-16.

Painting

When part of a window needs painting, Windows sends +wm-paint+ until the window is marked painted. A window drawn by other means, such as a Vulkan swapchain, calls validate-window when it gets +wm-paint+, and returns 1 for +wm-erasebkgnd+ so Windows does not paint over it:

((= message win32:+wm-paint+) (win32:validate-window window) 0)
((= message win32:+wm-erasebkgnd+) 1)