WinUI reference: For the full property surface and design guidance, see Windowing Overview.
Windows¶
Most Microsoft.UI.Reactor (Reactor) apps start with the single window created by
ReactorApp.Run. Larger desktop apps can open multiple native WinUI top-level
windows with WindowSpec and ReactorApp.OpenWindow, while keeping the same
declarative component model used inside a page.
Lifecycle basics¶
ReactorApp.Run<TRoot>(...) opens the primary window. ReactorApp.OpenWindow
opens a secondary window from the UI thread and returns a ReactorWindow handle
for imperative lifecycle operations.
var settings = ReactorApp.OpenWindow(
new WindowSpec { Title = "Settings", Width = 520, Height = 420 },
() => new SettingsWindow());
settings.Activate();
settings.Close();
Caveats:
Close,Show,Hide,Activate,Update, and mutators are UI-thread only.ReactorApp.PrimaryWindowis the first opened window; shutdown policy decides whether closing it exits the process.UseWindow()returns the owningReactorWindowinside a window component andnulloutside one (for example tray flyouts).
Sizing & resizing¶
Initial Width / Height are DIPs. Runtime size is controlled by SetSize,
chrome resize policy by ResizeMode, interactive aspect locks by AspectRatio,
and content-driven sizing by SizeToContent.
new WindowSpec
{
Title = "Preview",
Width = 640,
Height = 360,
ResizeMode = WindowResizeMode.CanMinimize,
AspectRatio = 16.0 / 9.0,
};
window.SetAspectRatio(4.0 / 3.0);
UseWindowAspectRatio(1.0); // lifetime-bound hook; unmount clears it
| API | Values / behavior |
|---|---|
ResizeMode |
CanResize, NoResize, CanMinimize |
AspectRatio |
double? width / height; honored during drag resize |
SizeToContent |
Manual, Width, Height, WidthAndHeight |
Caveats:
AspectRatiorejectsResizeMode.NoResize; no drag means no constraint to apply.AspectRatioandSizeToContentare mutually exclusive layout drivers.SizeToContentruns after layout, so the first frame can briefly use the initialWidth/Height; maximized windows ignore it and log a warning.- Min/max fields (
MinWidth,MaxHeight, etc.) win over content and aspect sizing.
Movement & placement¶
Use StartPosition for initial placement, SetPosition for imperative moves,
Position for read-back, and PositionChanged / UseWindowPosition() to react
to live moves.
var spec = new WindowSpec
{
Title = "Command Palette",
StartPosition = WindowStartPosition.CenterOnCurrent,
IsMovableByBackground = true,
};
var (x, y) = UseWindowPosition();
var drag = UseWindowDragMove();
Button("Drag window", drag);
IsMovableByBackground starts the OS move loop when a non-interactive part of
the root is pressed. Mark custom interactive regions with .Drag(false):
Placement options:
WindowStartPosition |
Meaning |
|---|---|
Default |
WinUI / shell chooses placement |
CenterOnPrimary |
Center on primary monitor |
CenterOnOwner |
Center on the owner window's monitor |
CenterOnCurrent |
Center on the cursor monitor |
Manual |
Use ManualPosition DIP top-left |
Persistence is opt-in and explicit:
var spec = new WindowSpec { Title = "Shell" }
.WithPersistence("main-window", fallback: WindowStartPosition.CenterOnCurrent);
window.SavePlacement(); // manual best-effort flush
Caveats:
- Position values are DIPs; mixed-DPI desktops have no single global DIP grid.
PositionChangedfires eagerly during drags; debounce in app code if needed.PersistenceIdalone is only identity. Placement restore/save requiresPersistPlacement = trueor.WithPersistence(...).
Z-order & visibility¶
WindowLevel selects a z-order tier. ShowInTaskbar and ShowInSwitcher are
separate because the taskbar button and Alt-Tab visibility are separate shell
concepts.
new WindowSpec
{
Title = "Palette",
Level = WindowLevel.Floating,
ShowInTaskbar = false,
ShowInSwitcher = true,
};
var isCovered = UseIsCovered(); // hint from ZOrderChanged
WindowLevel |
Behavior |
|---|---|
Normal |
Regular z-order |
Floating |
Stays above owner and other Reactor app windows as they activate |
AlwaysOnTop |
Win32 topmost tier |
ShowInTaskbar |
ShowInSwitcher |
Result |
|---|---|---|
true |
true |
Normal app window |
true |
false |
Taskbar button, no Alt-Tab entry |
false |
true |
Tool palette shape |
false |
false |
Transient / launcher / overlay shape |
Caveats:
ZOrderChanged.IsCoveredis a covered hint based on HWND insertion order, not pixel-accurate occlusion.Floatingis app-local. UseAlwaysOnToponly when you need global topmost.- Runtime taskbar visibility flips hide/show the HWND once so the shell refreshes.
Chrome & appearance¶
WindowStyle controls native chrome. WindowCornerStyle maps to the Windows 11
DWM corner preference. Backdrops are applied either on WindowSpec.Backdrop or
with a root .Backdrop(...) modifier.
new WindowSpec
{
Title = "HUD",
Style = WindowStyle.None,
IsMovableByBackground = true,
CornerStyle = WindowCornerStyle.Rounded,
Backdrop = BackdropChoice.Of(BackdropKind.DesktopAcrylic),
};
| API | Values |
|---|---|
WindowStyle |
Default, None, ToolWindow |
WindowCornerStyle |
Default, Square, Rounded, RoundedSmall |
BackdropKind |
None, Mica, MicaAlt, DesktopAcrylic, AcrylicThin, Transparent |
TitleBar(...) is the declarative custom title bar. When WindowSpec.ExtendsContentIntoTitleBar
is null (the default), mounting a TitleBar(...) element automatically sets
Window.ExtendsContentIntoTitleBar = true. Explicit true or false on the
spec wins over inference.
Caveats:
WindowStyle.NonewithoutIsMovableByBackgroundcan strand the user; Reactor warns but does not throw.WindowStyle.ToolWindowdefaults to hidden from the taskbar unlessShowInTaskbaris explicitly set.WindowCornerStyleis a Windows 11 DWM preference; Windows 10 ignores it.BackdropKind.Transparentfalls back to no backdrop when the referenced Windows App SDK does not expose a transparent backdrop type.
Taskbar integration¶
TaskbarItem groups all taskbar features while keeping the older shortcuts on
ReactorWindow for compatibility.
var taskbar = UseWindow()!.TaskbarItem;
taskbar.Description = "Build in progress";
taskbar.Progress.State = TaskbarProgressState.Normal;
taskbar.Progress.Value = 0.42;
taskbar.SetThumbnailToolbar([
new ThumbnailToolbarButton("pause", WindowIcon.FromPath("pause.ico"), "Pause", () => Pause())
]);
Facade members:
Progress— same instance asReactorWindow.Progress.Overlay— same instance asReactorWindow.Overlay.Description— forwards toITaskbarList3.SetThumbnailTooltip.SetThumbnailToolbar/ClearThumbnailToolbar— same toolbar pipeline as theReactorWindowshortcut methods.
Caveats:
- Shell COM calls are best-effort; Reactor keeps last-set managed state where relevant.
- Thumbnail toolbars support at most seven buttons.
- Overlay icons need HICON-compatible sources; resource URIs are not overlay HICONs.
Displays¶
ReactorDisplay exposes the current monitor layout in Reactor's DIP-oriented
shape and raises DisplayLayoutChanged when Windows reports a layout change.
var displays = UseDisplays();
var nearest = ReactorDisplay.NearestTo(window.Position.X, window.Position.Y);
DisplayInfo contains:
| Property | Meaning |
|---|---|
Id |
Win32 monitor id (for example \\.\DISPLAY1) |
IsPrimary |
Primary monitor flag |
WorkAreaDip |
Work area in approximate DIPs |
BoundsDip |
Full bounds in approximate DIPs |
Dpi |
Effective monitor DPI |
Caveats:
- Mixed-DPI virtual-screen X/Y values are approximate because Windows exposes physical pixels, not a global DIP coordinate system.
ReactorDisplay.Displaysis a snapshot. UseUseDisplays()to re-render on changes.NearestToaccepts DIP coordinates in Reactor's approximate display space.
Pickers¶
Picker hooks create WinUI storage pickers and initialize them with the owning window HWND, so the picker is modal to the correct window without app code doing HWND interop.
async Task OpenAsync()
{
var file = await UseFilePickerAsync(new FilePickerOptions(
FileTypeFilter: [".txt", ".md"]));
}
var folder = await UseFolderPickerAsync(new FolderPickerOptions());
Caveats:
- Picker hooks must be called on the owning window's UI thread.
- Reactor never accepts arbitrary HWNDs; it always uses
UseWindow().NativeWindow. - Tests should inject the picker service rather than opening native dialogs.
WPF / UWP migration map¶
| Prior stack concept | Reactor 054 shape | Notes |
|---|---|---|
WPF ResizeMode |
WindowResizeMode |
CanResizeWithGrip is intentionally omitted. |
WPF SizeToContent |
WindowSizeToContent |
Same four values. Min/max still win. |
WPF Topmost |
WindowLevel.AlwaysOnTop |
Floating adds app-local owner/sibling behavior. |
WPF WindowStyle.None |
WindowStyle.None |
Pair with IsMovableByBackground. |
WPF WindowStartupLocation.CenterScreen |
CenterOnCurrent |
Cursor monitor first. |
WPF manual Top / Left |
Position, SetPosition, UseWindowPosition |
DIPs, with mixed-DPI caveats. |
| WPF taskbar visibility | ShowInTaskbar |
Split from ShowInSwitcher. |
| Manual settings persistence | .WithPersistence(id) |
Opt-in, one line. |
TaskbarItemInfo |
TaskbarItem |
Facade over progress, overlay, description, thumb buttons. |
| UWP/WinUI picker HWND setup | UseFilePickerAsync / UseFolderPickerAsync |
Owning HWND is wired automatically. |
Finding and enumerating windows¶
ReactorApp.Windows // IReadOnlyList<ReactorWindow> snapshot
ReactorApp.PrimaryWindow // first window opened, or null after it closes
ReactorApp.FindWindow(key) // look up by WindowKey
Use WindowKey for any window you might want to find again. UseOpenWindow
lets a component declaratively own a secondary window's existence; tray icons use
UseTrayIcon and close automatically on unmount.
class SettingsHost : Component
{
public override Element Render()
{
// While this component is mounted, ensure a settings window keyed
// to "settings" is open. Re-renders that pass the same WindowKey
// reuse the same handle; the hook dedupes against the live window
// registry via FindWindow.
var settings = UseOpenWindow(
key: "settings",
spec: new WindowSpec { Title = "Settings", Width = 480, Height = 360 },
factory: () => new SettingsWindow());
return TextBlock(settings is null
? "(no UI dispatcher)"
: $"Settings open — id={settings.Id}");
}
}
Shutdown policy¶
// Call once at startup, before ReactorApp.Run. With OnLastSurfaceClosed the
// process keeps running while a tray icon or any window is alive; with
// Explicit you must call ReactorApp.Exit() yourself.
static class Startup
{
public static void ConfigureShutdown()
{
ReactorApp.ShutdownPolicy = ShutdownPolicy.OnLastSurfaceClosed;
}
}
| Policy | Process exits when... |
|---|---|
OnPrimaryWindowClosed (default) |
The primary window closes |
OnLastSurfaceClosed |
The last window and the last tray icon both close |
Explicit |
Never automatically; call ReactorApp.Exit() |
Tips¶
Memoize specs. A stable WindowSpec avoids unnecessary chrome updates.
Keep units in DIPs. Window size and position APIs use DIPs; shell style bits and DWM APIs use physical pixels internally.
Choose the narrowest z-order. Prefer Floating for app palettes; reserve
AlwaysOnTop for global overlays.
Use advanced recipes for rejected primitives. If you need true layered-window transparency or arbitrary region corners, see Advanced Windowing.
Next Steps¶
- Advanced Windowing — unsupported / interop-heavy window recipes
- Docking Windows — dock panes, floating document tear-outs, persistence
- Persistence — persisted scopes beyond window placement
- Dialogs and Flyouts — modal in-window UI
- Commanding — commands for title bars, tray menus, and window actions