Skip to main content

reactor-windowing

Reactor top-level windowing cookbook: WindowSpec, OpenWindow, draggable windows, borderless/tool windows, placement persistence, taskbar visibility, z-order, aspect ratio, SizeToContent, displays, taskbar integration, and picker HWND wiring.

Informações da origem

Repositório
microsoft/microsoft-ui-reactor
Última atividade na origem
7 de outubro de 2026 às 01:18
Idioma detectado do SKILL.md
inglês
Estrelas
663
Forks
57

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
reactor-windowing
description
Reactor top-level windowing cookbook: WindowSpec, OpenWindow, draggable windows, borderless/tool windows, placement persistence, taskbar visibility, z-order, aspect ratio, SizeToContent, displays, taskbar integration, and picker HWND wiring.
when_to_invoke
["open a window","secondary window","make a window draggable","remember window position","restore window placement","tool palette","command palette","window aspect ratio","borderless window","always on top","taskbar progress","file picker"]
# Reactor Windowing Use this skill when a task involves top-level windows, placement, shell chrome, taskbar features, displays, or native pickers. ## Core APIs ```csharp 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 ```csharp 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 ```csharp 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 ``` ```csharp 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`: ```csharp 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 ```csharp 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 ```csharp 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 ```csharp 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 ```csharp 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 ```csharp 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 ```csharp 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: ```csharp (TitleBar("Gallery") with { Content = HStack(8, AutoSuggestBox("", _ => {}).Width(200), Button(Icon(FontIcon("\uE713", fontSize: 16)), OnSettings) .AutomationName("Settings").IsDragRegion(false)), }).AutoRefreshDragRegions(); ``` ```csharp 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 ```csharp 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: ```csharp 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/`. ```csharp 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/`. ```csharp 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) ```csharp new WindowSpec { Title = "Player", Width = 960, Height = 540, AspectRatio = 16.0 / 9.0, }; UseWindow()?.SetAspectRatio(videoWidth / (double)videoHeight); UseWindowAspectRatio(16.0 / 9.0); ```
Ver no GitHub