Minimal window creation and message-loop support for content hosted by a raw
HWND.
Use windows-window when a Windows desktop application needs a small top-level window and message
loop to host a swap chain, WebView2 controller, Direct2D or Direct3D renderer, or another API that
accepts an HWND. It avoids depending on the full windows crate or generating
application-specific bindings for basic windowing.
The crate is not a general UI toolkit. Menus, controls, input policy, multi-window coordination, and specialized message handling remain the application's responsibility.
The crate targets Windows desktop applications. Create and drive a window on the thread that owns
its message queue. Interop code receiving Window::hwnd() must not retain the handle beyond the
Window lifetime.
Window creation attempts to set process DPI awareness to per-monitor v2. Set any different process
DPI policy before creating a Window.
The README contains dependency setup and the minimal create-and-run example.
Most integrations need the following sequence:
- Put shared renderer or controller state behind
Rc<RefCell<_>>or another UI-thread owner. - Build the window with
on_resizeandon_closeclosures that update and release the hosted content. - Call
create, then useclient_sizefor the initial content size. - Pass
hwndto the hosting API. - Choose
runfor an event-driven host orrun_withfor a render loop. - Drop hosted resources before the
Windowwhen their API requires the parent handle to remain valid.
on_resize receives client-area width and height in physical pixels. The callback also handles the
initial resize messages that arrive after the builder installs its state.
Window::new(title) returns a WindowBuilder. size sets the initial outer window size, while
client_size sets the size excluding non-client borders. style and ex_style replace the
defaults with raw WS_* and WS_EX_* values. The defaults are WS_OVERLAPPEDWINDOW and no
extended style. no_redirection_bitmap adds WS_EX_NOREDIRECTIONBITMAP for content supplied by
composition. visible(false) creates a hidden top-level window that can receive messages for
integrations such as notification-area icons.
on_message receives (hwnd, message, wparam, lparam) and returns Option<isize>. Return
Some(result) only when the application fully handled the message. Return None to use the
crate's built-in handling and DefWindowProcW. on_resize and on_move are focused alternatives
for WM_SIZE and WM_MOVE. If on_message handles either message, its focused callback does not
run.
on_close runs before default WM_CLOSE processing destroys the window. Use it to close or drop
hosted resources whose APIs require a live parent HWND. If on_message handles WM_CLOSE,
on_close does not run.
quit_on_close controls whether closing the window posts WM_QUIT. It defaults to true for
single-window applications. Dropping a window does not post WM_QUIT, so cleanup and failed
creation cannot terminate an application-owned loop. Set the option to false when window lifetime
and application lifetime differ, then call quit explicitly when the application should exit.
create registers the shared window class, creates the window, shows it when configured as
visible, and returns an error if creation fails. After native destruction, Window::hwnd returns
null and Window::client_size returns (0, 0). Window::close sends WM_CLOSE through the
configured close and quit behavior. Dropping a live Window calls DestroyWindow directly
without posting WM_QUIT.
| API | Use it when | Behavior |
|---|---|---|
run() |
Event-driven updates. | Blocks in GetMessageW until quit. |
run_with(render) |
Consecutive frames may be needed. | Drains messages, then calls render. |
pump() |
An external operation owns the wait. | Dispatches pending messages; never blocks. |
quit() |
Application state requires loop termination. | Posts WM_QUIT to the current thread. |
The run_with closure returns Result<bool>. Return Ok(true) to request another immediate frame,
or Ok(false) to block until a message arrives. Propagating an error exits the loop. This lets a
renderer switch between animation and an idle or occluded state without busy-waiting.
pump returns false after consuming WM_QUIT; the caller should then stop its outer loop.
Repeatedly calling pump without another wait mechanism spins the CPU.
Message dispatch is reentrant: a handler can call a Win32 API that sends another message before the
first callback returns. The crate temporarily removes user handlers while one runs. Nested messages
therefore use default processing instead of re-entering a closure or borrowing its captured
RefCell again. A nested WM_CLOSE is deferred until the active handler returns so hosted
resources can shut down before the native window is destroyed.
This also means a nested WM_SIZE triggered inside a handler does not invoke on_resize. Apply any
state update needed by that synchronous operation directly.
Handlers run across an extern "system" window-procedure boundary without catch_unwind. A panic
that escapes a handler aborts the process rather than unwinding through Win32. Return errors
through captured application state or catch a panic inside the closure if recovery is required.
Do not perform long blocking work in a message handler. It prevents painting, input, timers, and other components on the same UI thread from progressing.
- Use
hwnd()only with APIs that accept a borrowed parent or target handle. - Use
client_size()after creation to size the initial swap chain or child content. - Forward resize callbacks to
Controller::set_boundsforwindows-webview, or resize the relevant swap-chain buffers. - A composition host can use
no_redirection_bitmap()to avoid allocating an unused DWM redirection surface. - Raw input, paint, keyboard, mouse, DPI, and position behavior can be implemented through
on_message. This crate intentionally does not project message-specific argument types.
| Sample | What to study |
|---|---|
window-message |
Creation and message handling. |
standalone |
Swap-chain hosting and resize flow. |
direct2d |
Rendering only while visible. |
direct3d12 |
Binding a swap chain to the handle. |
dcomp |
Composition, custom style, and DPI. |
webview-minimal |
Controller lifetime and resize flow. |
This section is for contributors to windows-window.
src/bindings.rs is generated by tool-bindings from
crates/tools/bindings/src/window.txt. It contains the minimal flat Win32 surface needed for class
registration, creation, DPI setup, destruction, and message dispatch. The hand-written
window.rs depends only on windows-core.
One class is registered lazily for the process. A boxed state containing optional message, resize,
move, and close handlers and a shared liveness bit is stored in GWLP_USERDATA after
CreateWindowExW. wndproc clears the bit and removes the state on WM_NCDESTROY. Window::drop
destroys only the original native window while it remains live. This prevents a late drop from
acting on an HWND
value that Windows has recycled for another window.
Before invoking a callback, wndproc takes all handlers out of state. After the callback it reads
GWLP_USERDATA again because synchronous handling may have destroyed the window and freed the
state. It restores the handlers only when the state still exists. Keep this ordering when changing
dispatch behavior.
The crate does not catch panics in wndproc, add message-specific wrappers, or coordinate several
top-level windows. Those boundaries keep the crate small and its ownership rules explicit.