WizardControl
June 29, 2026 · View on GitHub
A discoverable, wizard-shaped control that runs a multi-step Flow.Wizard<TState>() inline — in a pane or region of an existing window — instead of opening a modal window per step.
Overview
WizardControl is the obvious door for "I want a wizard." When you reach for it, IntelliSense finds it, and a few lines later a multi-step wizard is running inside your layout:
var wiz = new WizardControl(); // or Controls.Wizard()
panel.AddControl(wiz);
var result = await wiz.Run(Flow.Wizard<InstallState>()
.WithStepIndicator()
.Step((ctx, s) => /* ... */)
.Step((ctx, s) => /* ... */));
if (result.Completed) Use(result.Value!);
else if (result.Cancelled) { /* user backed out */ }
It IS a FlowControl. WizardControl subclasses FlowControl the same way PanelControl subclasses CollapsiblePanel — a presetted, honestly-named specialization, not a wrapper or facade. It inherits the entire inline-flow surface unchanged (rendering, focus and child hosting, AsHost(), the idle/running/done lifecycle, and the Run<TState>(FlowWizardBuilder<TState>) entry point). It adds only a wizard-flavoured identity:
- a discoverable type name (
WizardControl/Controls.Wizard()), and - a wizard-friendly default
Placeholder([dim]No wizard running.[/]), which you can override.
It does not re-implement the wizard loop. wiz.Run(builder) is the inherited FlowControl.Run<TState>(...); the wizard engine (Flow.Wizard, the navigation loop, Back/Commit/Stay, the standardized button row) is reused as-is.
Generics live on the builder, not the control
The control is non-generic — like every other control it drops cleanly into the control tree, containers, and collections as a plain IWindowControl. The wizard's state type stays on Flow.Wizard<TState>() (the builder), where it is used to author strongly typed steps and surfaces as the returned FlowResult<TState>. This keeps the control honest (no vestigial type parameter on a tree node) while preserving full type safety where it matters — authoring the steps.
Lifecycle, focus, sizing
Inherited from FlowControl:
- States: idle → running → done, re-runnable. Idle/done show the
Placeholder. - Focus: a normal focus scope — Tab moves into the wizard's body/buttons and back out to siblings; the surrounding UI stays live (no modal block).
- Cancel: via the Cancel button or token (or removing the control mid-flow) →
FlowResult.Cancelled. Cancel is never an exception. - Sizing: fills its slot; the banner and button toolbar are fixed bands and the step body scrolls when it overflows.
- Re-entrancy: calling
Runwhile a wizard is already running throwsInvalidOperationException.
Scope: inline vs. modal
WizardControl runs a wizard inline (it is a FlowControl). To run the same wizard as a modal instead, skip the control and run the builder directly:
var result = await Flow.Wizard<InstallState>()
.WithStepIndicator()
.Step(/* ... */)
.Run(ws, parent); // modal (ModalWindowHost), or .WithSeamlessHost() for one reused window
So: reach for WizardControl when the wizard should live in a region of your UI; use Flow.Wizard<T>().Run(ws, parent) when it should be a popup.
See also: FlowControl, Composable Flows, GridControl