Application composition and lifecycle
Build and test commands are in CONTRIBUTING. See the reference index for related APIs. Examples in this reference use C++ unless stated otherwise. For C# and Rust coverage, use the binding reference.
Reusable controls
include\xui\application.hpp provides Window and Application::run.
Applications describe a control tree and callbacks. They do not supply a window procedure or drawing callbacks.
demo\gallery.cpp and demo\browser.cpp contain the application compositions.
Control::set_automation_id supplies an optional application identity for custom controls.
Without an override, custom controls use their stable element IDs.
Control::on_context_menu returns themed native menu items with actions, enabled state, checked state, and separators.
Native EDIT retains its system menu unless the control supplies a context-menu callback. Its native text provider does not change.
Nested native EDIT controls expose the public automation ID and name through their geometry override.
Control::on_focus reports a focus transition without a replacement window procedure.
Application example
For example, this application updates a label through a button callback:
#include "xui\application.hpp"
#include <windows.h>
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) {
xui::Window window({L"My application", {480, 240}});
auto content = std::make_shared<xui::Stack>(xui::Axis::vertical);
content->set_padding({24, 24, 24, 24});
content->set_spacing(12);
auto label = std::make_shared<xui::Label>(L"Ready");
auto button = std::make_shared<xui::Button>(L"Update label");
button->on_click([&] { label->set_text(L"Updated"); });
content->add(label);
content->add(button);
window.set_content(content);
return xui::Application::run(window);
}
Link the executable to xui_windows.
Include the manifest resource from demo\xui.rc, or supply an equivalent common-controls v6 and per-monitor-DPI manifest.
For MSVC builds that use this resource, set /MANIFEST:NO, as the gallery target does.
Ownership and property updates
The window retains its content through std::shared_ptr. A control has one layout parent and a stable Element::id.
Build the tree before Application::run. Set properties and use callbacks on that same UI thread.
The legacy Application::run(window) supports one active run on the thread.
Each legacy Window runs once, but separate windows can run in sequence.
ContentHost provides an explicit exception for a single replaceable root.
Window::replace_content changes only that host and preserves surrounding native peers.
It requires the UI thread and rejects replacement from active native input callbacks.
The binding contract describes candidate scopes and managed callback ownership.
The caller must not initialize COM as MTA.
Windows take activation and initial keyboard focus by default.
Before Run, Window::set_show_activated(false) shows a window without taking either.
WindowOptions::show_activated supplies the same initial choice in C++.
The C ABI uses xui_window_show_activated(window, 0) from xui_layout.h.
C# uses Window.SetShowActivated(false).
Later user activation remains available.
Changes during or after Run fail.
Callbacks must not outlive the objects that they reference.
Reference captures in the example remain valid during the blocking Application::run call.
Strong captures that refer back to their own control can create an ownership cycle.
Window teardown clears the host invalidator and disconnects accessible providers.
Retained controls remain valid after window destruction.
Enabled state and checked state request paint updates. Text and typography changes request layout for automatic sizes, or paint for preferred sizes. Size limits, spacing, and padding request layout and paint updates. The host combines pending updates. It has no continuous render loop. Opt-in animations share one window timer only during active transitions. Eligible indeterminate progress controls use a separate window-owned timer, without an explicit transition duration. The progress contract defines its lifecycle and reduced-animation behavior. Text stays on one line unless the text contains an explicit line break. An ellipsis marks text that exceeds the available width. The accessible name retains the full text.
Standard-control property setters do not call application action callbacks.
List selection operations call on_selection_change. View assignments call on_view_change.
Both callbacks observe the updated model. Item focus does not invoke the selection callback.
Pointer, keyboard, and UIA actions use the same activation behavior and enabled-state check.
Native committed text calls TextInput::on_change. IME preedit text does not call this callback.
Text input defaults to 1,024 UTF-16 units on Windows.
TextInput::set_maximum_length selects a limit from 1 to 32,767 units. Explorer address fields use 32,767 units.
The property setter preserves surrogate pairs at the limit and stops at the first NUL.
Callbacks can update other controls, change the theme, or request window closure.
Window::focus(control, select_all) requests native focus. The optional selection flag applies to text inputs.
Focus requests reject disabled controls, foreign controls, and closed windows.
Rejected select-all requests leave native selection and focus unchanged.
Window::copy_text requires an open window. Calls before startup or after closure throw before they open the clipboard.
The input-state methods on Control are backend boundaries, not application focus commands.
Startup errors and callback exceptions return a nonzero result. Window::error() supplies the error text.
Owned-child enumeration
Element::owned_children() returns an allocation-free OwnedChildren view of direct model children.
It is available on layouts as well as controls:
void visit_owned(const xui::Element& element) {
for (const auto& child : element.owned_children()) {
// child is a const std::shared_ptr<Element>& borrowed from its owner.
visit_owned(*child);
}
}
The view and its iterators do not copy shared ownership or keep the owner/tree alive.
Destroying the owner or changing its child storage invalidates outstanding views, iterators, and child references.
Do not mutate topology during enumeration. Nonstructural property, selection, visibility, and layout changes do not reorder children.
Use the owning UI thread once a tree belongs to a window.
size, empty, iteration, and indexed access do not allocate; at throws std::out_of_range for an invalid index.
As with std::span, operator[], front, and back require a valid index/nonempty view.
Standard owners enumerate non-null children in deterministic storage/slot order, without manufacturing children or realizing native peers.
Leaves and empty ContentHosts return an empty view. Null children, duplicate ownership, and cycles remain invalid.
Stack-derived layouts use insertion order (not Grid cell order); AdaptiveLayout stores content before navigation.
ScrollView and ContentView expose their content slot.
SplitView exposes its first and second ContentView wrappers, whose own children contain the supplied pane content.
Its typed first()/second() accessors return shared handles by value; enumeration borrows the stored handles.
Composite controls expose their existing composition order, including hidden/dormant retained slots.
Custom model classes can return a view over std::span<const std::shared_ptr<Element>>,
or use OwnedChildren::members<&Record::element>(records) for a span of records containing shared element handles.
This model contract does not add custom rendering/hosting support to Window.
Ownership is not a promise of visibility, native realization, hit testing, or UIA parentage.
Every PageView page is owned, while only the selected page is initially realized and previously visited native peers remain retained.
Closed Reveal and Expander content, collapsed split panes, and scrolled-out children remain owned.
Inspection registration includes inactive children; pointer inspection applies selection, visibility, and viewport/pane clipping separately.
Popup/modal content has its own coordinated root. A ComboBox owns only its inline editor in this tree;
its popup is a separately coordinated root, not another layout child.
Native text clear-button adornments belong to the host peer, not the authored child list.
Their retention, visual ordering, clipping, and retirement remain explicit host responsibilities.
Window icons
Window::set_file_type_icon(extension, directory) sets the small and large native HWND icons.
The C# method is Window.SetFileTypeIcon. Rust uses Window::set_file_type_icon.
The C ABI exports xui_window_file_type_icon.
An extension such as .txt selects its type-association icon without access to the target file.
An empty extension selects the stock document icon. An empty extension with directory=true selects the stock folder icon.
The stock path does not resolve a file association or invoke file providers.
Extensions start with a dot, contain at most 255 UTF-16 units, and cannot contain control characters or path separators. Directory icons require an empty extension. The method requires the window's UI thread, before Show or while the window is open. Invalid arguments preserve the existing icons. Each window owns its icon handles, replaces their sizes on DPI changes, and releases them on closure. Custom title bars retain their own visual controls. The HWND icons supply Windows taskbar and window-switching surfaces.
Borrowed HWND and transparent windows
The Windows host exposes Window::native_window() as a borrowed HWND on the creating UI thread while its state is open.
It is not available before Application::show or during/after closure, and callers must never destroy it, replace its WndProc, or assume that changing window styles also changes XUI's renderer.
Window::on_native_created(callback) runs once after CreateWindowExW and before first paint or ShowWindow.
Register it before Show/Run to configure Win32 attributes without a frame flash. The callback receives the HWND but must not call XUI content or input APIs until the window is open. A callback failure prevents Show/Run and propagates as an error.
WindowOptions::transparent (or Window::set_transparent(true) before Show/Run) selects a borderless WS_POPUP | WS_EX_LAYERED top-level window. It cannot be combined with XUI's custom title bar.
The opt-in renderer clears to transparent and presents premultiplied-alpha pixels from an offscreen Direct2D DC target using UpdateLayeredWindow; normal windows keep their existing HWND renderer.
The first layered frame is painted before Show so the empty HWND does not briefly intercept input.
Style an inset Stack with an opaque background and rounded corners to make a floating card; unpainted pixels outside it show the desktop and pass pointer hit testing through.
The layered buffer is capped at 4,096 physical pixels on each edge; exceeding the limit fails explicitly.
Windows high contrast paints a solid system-color background instead of leaving text over an unpredictable desktop.
This is a software-rendered per-pixel surface, not Mica, acrylic, or a live blur. The sample uses a solid border rather than CmdPal's compositor shadow.
Ordinary windows can request a system backdrop instead; see Mica and acrylic materials.
Requesting a backdrop on a transparent window before Show replaces this layered surface with a borderless material window.
Window::set_drag_region({x, y, width, height}) marks one client-DIP rectangle as a caption drag target; call it before Show/Run and leave controls out of that rectangle.
It requires transparent hosting and finite, positive bounds within 16,000 DIPs of the client origin.
The native parent and its transparent child peers agree on hit testing. XUI still owns child HWNDs, native text editing, focus, accessibility, and teardown.
The C# floating-card sample exercises the public binding and interop handle.
Independent application windows
An instance of Application owns one STA message dispatcher.
create_window(options) creates a window associated with that application.
After the caller builds its content, show(window) creates a visible ownerless top-level window without a nested message loop.
run() services all shown windows until the last one closes and deferred cleanup completes.
The caller keeps each C++ Window alive and keeps its callback captures valid after show returns.
Every window has its own control tree, native text peers, focus, renderer, tasks, and post queue.
The same retained tree cannot belong to two live native hosts.
Window states are created, open, closing, and closed.
Closure before the first show or run also rejects later mutations.
on_closed runs once after native destruction and after active window dispatch unwinds.
Window::post rejects closed windows and discards pending callbacks on closure.
Application::post supports deferred cleanup after a window closes.
Both post methods accept worker-thread calls while their owner remains alive.
Ownerless windows can move independently, participate in normal Shell window switching, and survive closure or minimization of another window.
Window closure does not request application shutdown.
Application::shutdown() closes all associated windows and rejects new windows.
An application instance runs once. Nested application runs and legacy runs inside an application context are invalid.
Lifecycle calls and destruction belong to the creating UI thread.
The caller must not initialize that thread as MTA.
A window callback failure closes that window without closing healthy windows.
The application retains the failure and returns a nonzero run result.
Application::error() supplies the retained messages.
An application-post callback failure requests application shutdown.
Final native-runtime cleanup occurs after all windows close and before OLE teardown, not after each window.
Owned native file dialogs
Window::show_open_file_dialog and show_save_file_dialog show the Windows Shell file dialog.
Both methods take FileDialogOptions and return std::optional<std::wstring>.
A value contains one absolute filesystem path. An empty result means cancellation, including owner closure.
Native failures throw. These methods do not read or write the selected file.
The Save dialog requests overwrite confirmation but leaves the actual write and conflict policy to the application.
The application supplies filters and extensions. XUI has no application-specific file type. This example belongs in a callback while the window runs:
xui::FileDialogOptions options;
options.title = L"Open component";
options.filters = {{L"Components", L"*.xui"}, {L"All files", L"*.*"}};
options.default_extension = L"xui";
const auto selected = window.show_open_file_dialog(options);
For Save, suggested_name supplies a leaf filename. An optional initial_directory selects an existing absolute drive or UNC directory.
An empty directory leaves the initial location to Windows. A missing or invalid directory produces an error, not a different location.
The first filter is selected. Patterns accept *, *.*, or semicolon-separated *.extension entries, with no spaces.
default_extension omits the leading dot and wildcards. A compound extension is permitted.
All option strings require valid UTF-16 without NUL or control characters.
The limits, in UTF-16 units, are 256 for titles, 128 for filter names, and 512 for filter patterns.
Default extensions permit 64 units, suggested names permit 255, and directory and result paths permit 32,767.
There are at most 32 filters. Empty filter names, malformed patterns, device paths, and invalid filename characters produce errors.
FileDialogOptions::validate checks the options without opening a window.
The caller must use the window's UI thread during its active run.
The window must be visible and enabled, with no active text composition, popup, file dialog, or content replacement.
Calls before startup, during native synchronization, or after closure produce errors.
The existing COM and manifest requirements apply. The
The native dialog owns a modal loop and disables its XUI owner.
Messages and native callbacks can run inside that loop, but ordinary Window::post delivery waits until it returns.
Nested file dialogs and content replacement are rejected. Option data is copied before native dispatch.
Owner closure requests native cancellation. C++ owner destruction defers native teardown until the modal call returns.
The method retains its backend, not the public Window. Callers must not use a deleted Window after the method returns.
Focus returns to the surviving native child only while the owner remains active. XUI does not reactivate another window.
The binding contract defines C results, managed disposal, and content-scope restrictions.
Content sizes and constraints
Labels, buttons, and toggles use automatic content sizes by default.
DirectWrite supplies glyph widths and line heights through the Control::set_text_measurer backend boundary.
The core has no Windows dependency and does not estimate glyph widths.
Headless callers can supply a TextMeasurer. Without a measurer, controls use their original preferred sizes.
Each native label, button, and toggle retains one text layout. Text or typography changes replace that layout. Hover, focus, scrolling, and paint reuse it. DPI changes reuse DIP measurements and update the native input font. Window teardown removes measurer callbacks before it releases the renderer.
Parent constraints take precedence over minimum and fixed sizes. No child can force an overflow from a smaller allocation.
A new minimum raises a smaller maximum. A new maximum lowers a larger minimum.
set_auto_size does not clear limits. Zero minimum and infinite maximum values remove the limits.
TextInput, FileList, and ScrollView retain their preferred viewport sizes.
A Stack measures non-flex children first, in order, against the remaining main-axis space.
An explicit Stack preferred size replaces natural measurement unless set_auto_size(true) overrides it.
That preferred size includes padding and remains subject to parent bounds and size limits.
Flex children share the remaining main-axis space. On the cross axis, children stretch up to their maximum size.
Maximum limits do not redistribute unused flex space.
In an unbounded main axis, flex children use their natural desired size instead of an infinite share.
A ScrollView gives its content a bounded width and an unbounded height.
This model has no CSS cascade, automatic wrapping layout, or reactive property graph.
This composition gives the button its content width and the form the remaining height:
auto root = std::make_shared<xui::Stack>(xui::Axis::vertical);
auto form = std::make_shared<xui::Stack>(xui::Axis::vertical);
form->set_spacing(12);
form->add(std::make_shared<xui::Label>(L"Workspace"));
form->add(std::make_shared<xui::TextInput>(L"Workspace name"));
auto actions = std::make_shared<xui::Stack>(xui::Axis::horizontal);
actions->add(std::make_shared<xui::Button>(L"Apply preferences"));
form->add(actions);
auto viewport = std::make_shared<xui::ScrollView>(form, L"Workspace preferences");
root->add(viewport, 1);
window.set_content(root);
Inline overlap
Stack and Grid children paint in declared order, from back to front. Each child paints its background, border, and descendants as one subtree. Later siblings cover earlier siblings, including collection rows and native editor pixels. An opaque panel therefore covers the content beneath its bounds. Ancestor viewports and authored rounded corners still clip that subtree.
Native sibling order matches this visual order. An inline Reveal does not require a popup or an application-specific HWND operation. Opening, closing, scrolling, and resizing an inline overlay do not activate the window or move keyboard focus. Native editors retain their HWND, text, selection, and composition state. Visible controls outside the overlay remain interactive. An explicit pointer or keyboard action retains the normal focus behavior of its target.
Popups and compact navigation overlays retain their separate foreground order. Inline overlap does not change popup dismissal, modal input, or native text composition.
Retained scrolling
ScrollView accepts one retained Element, including a nested Stack.
Its content has one layout parent. Null content, duplicate ownership, and cycles are invalid.
offset, extent, and maximum_offset use DIPs.
set_offset, scroll_by, and reveal clamp the offset to the content range.
The viewport reserves 12 DIPs for its vertical scrollbar. A larger viewport clamps obsolete offsets automatically.
The mouse wheel follows Windows wheel settings: one line is 16 DIPs, and page scrolling uses the
viewport height. Partial wheel deltas move proportionally without waiting for a whole notch.
FileList and retained collection and grid rows can stop between items. The scrollbar supports
continuous thumb drag and track paging.
With viewport focus, arrow keys, Home, End, Page Up, and Page Down move the viewport.
Page keys also work from ordinary descendants. Native input retains its text-editing keys and IME path.
Tab, Shift+Tab, Window::focus, and UIA focus reveal the target through ancestor viewports.
A disabled viewport rejects input and disables descendant actions.
The shared renderer clips content against every ancestor viewport. Each viewport also owns a native parent HWND. Windows clips child EDIT and caption pixels against that parent. Scrolling updates native child positions without copying old pixels to intermediate positions. The shared frame includes custom controls, captions, and native field pixels before presentation. The previous complete frame remains visible until the new frame replaces it. A geometry override supplies clipped native EDIT bounds and offscreen state to UIA. The override preserves the native accessible name, value pattern, and available native text patterns. It does not replace editable text, selection, undo, or IME.
The viewport exposes UIA ScrollPattern, scroll percentages, viewport fraction, enabled state, and scroll-property events.
Custom descendants expose ScrollItemPattern for reveal without selection or focus.
Custom controls and embedded FileList rows expose clipped bounds.
The scrollbar has no separate UIA element.
ScrollView does not virtualize arbitrary content. All its controls and native peers remain in memory.
FileList remains the virtualized choice for large collections. ScrollView does not create a control for each FileList row.
The gallery has 17 searchable examples, including measured labels, native fields, disabled controls, and a scrollable preferences form.
F6 cycles dark, light, and explicit high-contrast colors outside grids. Within a grid, F6 selects the header.
Input and accessibility
Buttons and checkboxes support hover, pressed state, pointer capture, drag-out cancellation, and capture loss. Window deactivation and cancellation also cancel pending presses. Tab and Shift+Tab move focus through enabled controls. Space activates a focused button or checkbox on release. Enter activates a focused button. Enter does not change a checkbox. The native EDIT retains Windows text selection, undo, clipboard, IME, and native UIA text behavior. The bridge keeps activation on the top-level window when the native UIA proxy activates the EDIT HWND. External UIA focus restores a minimized host and leaves keyboard focus on EDIT. This path does not replace the native Value or Text providers. Windows foreground-activation restrictions still apply.
The bridge keeps the composition guard active through native WM_IME_ENDCOMPOSITION processing.
It then publishes the committed value. Repeated end notifications do not repeat an unchanged text callback.
Same-DPI theme changes reuse the native font. DPI changes replace the font without replacing the EDIT HWND.
Custom controls expose UIA text, button, or checkbox roles. Their accessible names track the public names. Their automation IDs use the stable element IDs. Custom control fragments expose explicit focus, Invoke, and Toggle actions where applicable. The HWND adapters supply native tree placement. Native EDIT keeps its own provider identity and a preceding native label. The backend publishes name, enabled, focus, and toggle changes, plus button invocation events. Retained providers reject actions after window teardown. Lists also publish scroll percentage, viewport fraction, scroll availability, selection, and item-focus property changes. Unchanged properties do not produce framework property events. Enabled high-contrast checkmarks use the selection-text color. A pressed focus outline also uses that color.
Framework boundaries
xui_core has no Windows dependencies. Its public API includes stable element identity, explicit property updates, and two-pass layout.
Stack supports horizontal and vertical layout, preferred sizes, flex space, gaps, and padding.
Elements distinguish layout invalidation from paint invalidation. The host combines pending layout requests and uses normal invalid paint regions.
The application has no continuous render loop.
FileList owns navigation and viewport behavior. FileListModel owns items, filter results, and selection identity.
Item focus is separate from selection. An accessibility focus action does not select or deselect an item.
The renderer submits visible rows plus a small buffer. It does not create an element, text layout, or UIA provider for every item.
Items and filter indices remain in memory. This is visual virtualization, not a paged storage system.
Commands remains available for command tables.
The browser connects shared callback actions through Window::on_key and Control::on_context_menu.
The Windows backend separates drawing, list adaptation, asynchronous delivery, native text input, and UI Automation.
Directory enumeration belongs to the sample service. The image backend owns image-file access and WIC decoding.
Each ViewTask has one worker for enumeration, sorting, snapshot construction, and filtering outside the UI thread.
Generation numbers reject stale source data and stale filter results.
Window closure requests cancellation without a UI-thread join. A filesystem driver that ignores cancellation can delay final process cleanup.
The UI thread submits committed text without a typing timer. It keeps the previous view until the current result arrives.
NativeEditBridge uses a real Windows EDIT control. Windows owns its IME, text selection, Unicode input, undo, clipboard behavior, and accessibility.
This bridge is not a custom text engine or a completed TSF implementation.
The search field has a 1,024-character limit. Framework text drawing never substitutes for editable text.
The empty-field hint uses the theme color. It is decorative text, not part of the editable value.
The custom list exposes UIA list semantics, selection, scroll, fragment navigation, and on-demand item providers.
Selection is optional and single-item. UIA removal clears only the addressed selected item.
Item providers retain stable IDs instead of recycled row positions.
Native EDIT supplies search-field accessibility. The public Label provider supplies status-text accessibility.
The window uses per-monitor DPI, system high-contrast colors, and graphics-target recreation.
Known limits
The legacy entry point supports one active window. An explicit application context supports multiple ownerless windows on the same UI thread.
It does not provide a general styling system, a reactive property graph, or a custom text engine.
Filter matching uses wide-character case conversion, not full Unicode normalization or linguistic search.
Filtering still examines every source row. It runs on the worker, not the UI thread.
The index format supports at most 4,294,967,295 rows, but available memory imposes a much smaller practical limit.
All source items remain in memory. The million-row benchmark is not a million-file desktop test.
Very large scroll extents lose subpixel precision because offsets use floating-point values.
Cancellation cannot interrupt one allocator operation, one substring search, or a filesystem driver that ignores cancellation.
The ViewTask cleanup thread waits for cancelled workers. Process exit does not have a fixed shutdown deadline.
UIA clients that enumerate the complete tree still perform work for every item.
UIA supports the patterns that this demo uses, not every Windows control pattern.
Application context menus use the shared theme. Native EDIT keeps its Windows menu when no custom menu callback exists.
Custom touch gestures, drag-and-drop, and file modifications are outside this milestone.
The explorer uses Windows mouse promotion for touch input. Dedicated touch-hardware coverage remains incomplete.
The image sample replaces folders through its path field.
High-contrast changes and graphics-target recreation have implementation paths, but hardware coverage is not exhaustive.
After server exit, the Windows native HWND focus proxy can return S_OK for a stale SetFocus call.
The native Value provider returns UIA_E_ELEMENTNOTAVAILABLE, and the stale focus call must leave another window's focus unchanged.
Custom providers return UIA_E_ELEMENTNOTAVAILABLE after disconnection.
A native focus HRESULT alone is not proof of live keyboard focus.