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 typewindow-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.:visibleshows it at once.:popup,:child,:caption,:border,:system-menu,:thick-frame,:minimize-box,:maximize-box,:clip-children,:clip-siblings,:disabled,:minimize,:maximizeand:overlappedare the parts. Styles combine as their bits do, so overlapping ones, such as:overlapped-windowand:caption, may be given together.:extended-styles: extended styles, of the typeextended-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:popupwindow, its owner, which it stays above and goes with;:messagefor a message-only window, which is never shown and only receives messages, a convenient target for posting work to a thread; ornil.
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-sizeis the client area: the inside of the window, where a program draws.window-boundsis the whole window on the screen, title bar and borders included.set-window-boundsmoves and sizes the whole window. Give:xand:ytogether, and:widthand:heighttogether; what you leave out stays. It neither activates the window nor changes which windows are above it.window-size-for-client-sizeadds the title bar and borders of a window with the given:stylesand:extended-styleson a display of:dpito 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-boundsreturns 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-areareturns 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)