SuperAPI AI Directory

reactor-windowing

Frontend & websites

View repository

Updated 10-16 00:31

About this skill

Reactor Windowing

Use this skill when a task involves top-level windows, placement, shell chrome, taskbar features, displays, or native pickers.

Core APIs

var win = ReactorApp.OpenWindow(
    new WindowSpec { Title = "Settings", Width = 520, Height = 420 },
    () => new SettingsWindow());

win.Activate();
win.Close();

WindowSpec is immutable startup intent. ReactorWindow is the live handle for runtime mutators (SetSize, SetPosition, SetAspectRatio, BeginDragMove, SavePlacement, Update).

Size and resize

new WindowSpec
{
    ResizeMode = WindowResizeMode.CanMinimize,       // CanResize | NoResize | CanMinimize
    AspectRatio = 16.0 / 9.0,
    MinWidth = 480,
    SizeToContent = WindowSizeToContent.Manual,      // Manual | Width | Height | WidthAndHeight
};

UseWindow()?.SetAspectRatio(4.0 / 3.0);
UseWindowAspectRatio(1.0); // scoped; unmount clears

Rules:

  • AspectRatio cannot combine with ResizeMode.NoResize.
  • AspectRatio cannot combine with SizeToContent.
  • SizeToContent ignores maximized windows and may settle one frame after mount.

Movement, drag, and persistence

new WindowSpec
{
    StartPosition = WindowStartPosition.CenterOnCurrent,
    IsMovableByBackground = true,
};

var (x, y) = UseWindowPosition();
var drag = UseWindowDragMove();
Button("Drag", drag);
Border(customEditor).Drag(false); // opt out of background drag
var spec = new WindowSpec { Title = "Shell" }
    .WithPersistence("shell-main", WindowStartPosition.CenterOnCurrent);

UseWindow()?.SavePlacement();

Persistence requires PersistPlacement; use .WithPersistence(...) for the common case. PersistenceId alone is only identity for persistence systems.

Placement goes to an auto-picked store: ApplicationData.Current.LocalSettings when packaged, else a JSON file under %LOCALAPPDATA%/<ProcessName>/. That default is keyed on the process name, so renaming the exe strands saved layouts. Unpackaged apps can opt into a stable publisher/product identity before the first OpenWindow:

using Microsoft.UI.Reactor.Hosting.Persistence;

ReactorApp.WindowPersistenceStore =
    new UnpackagedAppDataStore(publisher: "Contoso", product: "TimeTracker");

Opt-in rather than default: the two stores key data differently, so switching does not migrate existing layouts. Multiple instances sharing one publisher/product are safe — writes are serialized across processes, so one window's save cannot drop another's entry. Two windows sharing a PersistenceId still overwrite each other, by design.

Z-order, taskbar, and chrome

new WindowSpec
{
    ShowInTaskbar = false,
    ShowInSwitcher = true,
    Level = WindowLevel.Floating,          // Normal | Floating | AlwaysOnTop
    Style = WindowStyle.ToolWindow,        // Default | None | ToolWindow
    CornerStyle = WindowCornerStyle.RoundedSmall,
};

Notes:

  • Floating stays above owners and sibling Reactor app windows.
  • ToolWindow hides from the taskbar by default unless ShowInTaskbar is explicit.
  • WindowStyle.None should normally set IsMovableByBackground = true.
  • UseIsCovered() is a z-order hint, not pixel-accurate occlusion.

Title bar and backdrop

VStack(
    TitleBar("My App"),
    Body());

A TitleBar(...) element infers ExtendsContentIntoTitleBar = true when the spec value is null. Explicit true or false wins.

Close-safety caveat (#537): ExtendsContentIntoTitleBar = false with a mounted TitleBar(...) is allowed and previously crashed on close (STATUS_HEAP_CORRUPTION); Reactor now flips the window back into content-extended mode just before the native close (every close/exit/dispose path) so it is safe. Prefer omitting TitleBar(...) when you want the system title bar.

Title bar icon

ReactorApp.Run<App>("My App", icon: WindowIcon.FromPath("Assets/AppIcon.ico"));

TitleBar("My App");            // inherits the window icon -- nothing to declare
TitleBar("My App").NoIcon();   // opt out, for a deliberately bare title bar

A TitleBar(...) with no .Icon(...) inherits the window's icon: WindowSpec.Icon if declared, otherwise the Assets\AppIcon.ico convention beside the app. Do not restate the app icon on the title bar — that is the default. Use .Icon(...) only to show a different mark there.

Not inherited: an icon that exists only as an executable PE resource (<ApplicationIcon>), because that stage yields a raw HICON with no path and a XAML IconSource needs an image source; and an embedded window (WindowSpec.Embed), which never receives a window icon at all.

Icon sources

WindowIcon.FromPath("Assets/AppIcon.ico");          // file beside the app
WindowIcon.FromResource("ms-appx:///Assets/A.ico"); // packaged resource
WindowIcon.FromBytes(icoOrPngBytes);                // encoded data in memory
WindowIcon.FromRgba(pixels, 16, 16);                // raw RGBA8, top-down

Not every surface takes every kind, because they need different primitives:

Surface FromPath FromResource FromBytes / FromRgba
Window caption / Alt-Tab yes yes no
Tray icon, taskbar overlay, thumbnail toolbar yes no yes
Jump-list entry unpackaged only packaged only no

The three shell surfaces need a raw HICON (LoadImageW on a file, or CreateIconFromResourceEx on in-memory data), neither of which reads an ms-appx: URI. Jump lists need a Uri. AppWindow.SetIcon needs a filesystem path. An unusable source is skipped with a diagnostic, never thrown — on the window that means falling through to Assets\AppIcon.ico or the PE icon, so a binary icon: leaves the window no barer than declaring none.

Reach for the binary factories when the icon is an embedded resource, a download, or drawn at runtime: they avoid writing a temporary file. The bytes are copied at construction and held for the WindowIcon's lifetime.

Tall title bar

TitleBar("My App").WithNavigation(nav).PaneToggleButtonVisible(true).Tall();
new WindowSpec { ExtendsContentIntoTitleBar = true, TitleBarHeight = WindowTitleBarHeight.Tall };

.Tall() / .HeightOption(WindowTitleBarHeight) (Standard / Tall / Collapsed) is the layout for a title bar hosting a back button or pane toggle. It sets both the system caption (AppWindow.TitleBar.PreferredHeightOption) and the WinUI title-bar control's own height — the control does not follow the caption, so setting one alone leaves them disagreeing. An explicit .Height(...) wins over the implied 48; WindowSpec.TitleBarHeight wins over the element.

Requires a content-extended window — the native setter throws ERROR_INVALID_STATE otherwise. Reactor applies it after its own content-extension flip and warns instead of throwing, so prefer this over setting PreferredHeightOption from a UseEffect.

Caption button theme

TitleBar("My App").PreferredTheme(isDark ? WindowTitleBarTheme.Dark : WindowTitleBarTheme.Light);
new WindowSpec { TitleBarTheme = WindowTitleBarTheme.Dark };

WinUI does not carry an element's RequestedTheme to the system caption buttons (min/max/close), so a root themed opposite to the system keeps system-themed buttons. .PreferredTheme(WindowTitleBarTheme) (Legacy / UseDefaultAppMode / Light / Dark) writes AppWindow.TitleBar.PreferredTheme on mount and on change — no UseEffect. Opt-in: with nothing declared Reactor never writes the property, and removing a declaration restores the value the caption had before Reactor first applied one. WindowSpec.TitleBarTheme wins over the element. Unlike the height, it does not need a content-extended window.

Custom title-bar content and drag regions

TitleBar(...) accepts custom Content. Interactive controls are excluded from the drag region automatically (WinApp SDK ≥ 2.1.3). Override per element with .IsDragRegion(false) (force clickable) or .IsDragRegion(true) (force draggable), and add .AutoRefreshDragRegions() when the content changes across renders:

(TitleBar("Gallery") with
{
    Content = HStack(8,
        AutoSuggestBox("", _ => {}).Width(200),
        Button(Icon(FontIcon("\uE713", fontSize: 16)), OnSettings)
            .AutomationName("Settings").IsDragRegion(false)),
}).AutoRefreshDragRegions();
VStack(...).Backdrop(BackdropKind.Mica);
new WindowSpec { Backdrop = BackdropChoice.Of(BackdropKind.DesktopAcrylic) };

BackdropKind.Transparent falls back to no backdrop when unsupported by the referenced Windows App SDK.

Taskbar, displays, and pickers

var taskbar = UseWindow()!.TaskbarItem;
taskbar.Description = "Exporting";
taskbar.Progress.State = TaskbarProgressState.Normal;
taskbar.Progress.Value = 0.25;

var displays = UseDisplays();
var nearest = ReactorDisplay.NearestTo(window.Position.X, window.Position.Y);

var file = await UseFilePickerAsync(new FilePickerOptions(FileTypeFilter: [".txt"]));
var folder = await UseFolderPickerAsync(new FolderPickerOptions());

Picker hooks must run on the owning window's UI thread and use the owning HWND; there is no arbitrary HWND parameter.

Render errors

Outside an ErrorBoundary, a render failure shows a built-in fallback with the full exception text. Replace it app-wide, per window, or per host; a host's own handler wins, and null on the window/host falls through to the app default:

ReactorApp.DefaultRenderErrorHandler = e => TextBlock("Something went wrong.");
ReactorApp.OpenWindow(spec with { RenderErrorHandler = e => SettingsErrorView(e) }, render);
window.Host.RenderErrorHandler = e => { e.Propagate(); return null; }; // → OnUnhandledException

Return null to keep the built-in fallback. If the handler (or the element it returns) throws, Reactor shows a neutral message without exception details. The handler runs again each time a failing component re-renders, so de-duplicate telemetry. If OnUnhandledException declines a propagated error, a re-render ends the process (WinUI does not raise Application.UnhandledException for it).

Recipe: Command Palette

PowerToys Run-style launcher. See samples/apps/command-palette-window/.

new WindowSpec
{
    Style = WindowStyle.None,
    IsMovableByBackground = true,
    Level = WindowLevel.AlwaysOnTop,
    CornerStyle = WindowCornerStyle.Rounded,
    StartPosition = WindowStartPosition.CenterOnCurrent,
    ShowInTaskbar = false,
    ShowInSwitcher = false,
};

Recipe: Tool Palette

Photoshop-style owned floating palette. See samples/apps/tool-palette/.

var main = ReactorApp.OpenWindow(new WindowSpec { Title = "Editor" }, () => new Editor());

ReactorApp.OpenWindow(new WindowSpec
{
    Title = "Tools",
    Owner = main,
    Style = WindowStyle.ToolWindow,
    Level = WindowLevel.Floating,
    CornerStyle = WindowCornerStyle.RoundedSmall,
}, () => new ToolPalette());

Recipe: Media Player (aspect-locked)

new WindowSpec
{
    Title = "Player",
    Width = 960,
    Height = 540,
    AspectRatio = 16.0 / 9.0,
};

UseWindow()?.SetAspectRatio(videoWidth / (double)videoHeight);
UseWindowAspectRatio(16.0 / 9.0);