Beep Wizard System
A complete, fully decoupled wizard framework with four visual styles, async step navigation, pluggable validation, an inter-step data bus, and smooth slide/fade animations.
Overview
The Beep Wizard System is architected as a decoupled core-engine / UI-host pair:
- WizardManager — static façade; creates and shows wizards in one call.
- WizardInstance — the runtime engine; owns navigation, validation, and state. Contains no UI code.
- WizardFormFactory — instantiates the correct form host for the chosen
WizardStyle. - IWizardFormHost — interface that all four form types implement; keeps the engine UI-agnostic.
- WizardContext — typed key/value data bus shared across all steps.
TheTechIdea.Beep.Winform.Controls.WizardsForm types: HorizontalStepper · VerticalStepper · Minimal · Cards
Entry point:
WizardManager.ShowWizard(config) or WizardManager.CreateWizard(config)
Architecture
// Layer overview
WizardManager // static façade — create / show / close wizards
└─ WizardInstance // core engine: navigation, validation, state, events
├─ WizardConfig // immutable construction config + step list
├─ WizardContext // typed key/value data shared between steps
└─ IWizardFormHost─┬─ HorizontalStepperWizardForm
├─ VerticalStepperWizardForm
├─ MinimalWizardForm
└─ CardsWizardForm
Decoupling principle
WizardInstance calls only IWizardFormHost methods (UpdateUI, ShowValidationError, GetContentPanel, etc.). Swapping a visual style at creation time is the only change needed — no engine logic changes.
Wizard Styles (WizardStyle enum)
| Value | Form Class | Description |
|---|---|---|
HorizontalStepper | HorizontalStepperWizardForm | Numbered step pills across the top, animated slide transition between steps. |
VerticalStepper | VerticalStepperWizardForm | Timeline-style vertical step list on the left panel, content area on the right. |
Minimal | MinimalWizardForm | Clean single-line progress indicator, maximises content area, no step sidebar. |
Cards | CardsWizardForm | Clickable step-card deck on the left; selecting a card jumps to that step. |
Key Classes & Models
WizardConfig
All wizard settings passed to WizardManager.CreateWizard(). Immutable after creation.
| Property | Type | Default | Description |
|---|---|---|---|
Key | string | (auto GUID) | Unique key for the wizard instance. Used by WizardManager.GetWizard(). |
Title | string | "Wizard" | Title displayed in the wizard heading. |
Description | string | "" | Optional sub-heading or description text. |
Size | Size | 900×650 | Initial form size. |
Style | WizardStyle | HorizontalStepper | Visual layout style (see above). |
ShowProgressBar | bool | true | Show or hide the progress bar. |
ShowStepList | bool | true | Show or hide the step list / sidebar. |
AllowCancel | bool | true | Enable the Cancel button. |
AllowBack | bool | true | Enable the Back button. |
AllowSkip | bool | false | Show Skip button for optional steps. |
ShowHelp | bool | false | Show a Help button. |
HelpUrl | string | "" | URL opened when Help is clicked. |
NextButtonText | string | "Next" | Default Next button label. |
BackButtonText | string | "Back" | Default Back button label. |
FinishButtonText | string | "Finish" | Finish button label (shown on last step). |
CancelButtonText | string | "Cancel" | Cancel button label. |
SkipButtonText | string | "Skip" | Skip button label. |
ShowInlineErrors | bool | true | Show validation errors inline instead of MessageBox. |
AutoHideErrors | bool | true | Hide validation error panel when navigating to the next step. |
Steps | List<WizardStep> | [] | Ordered list of wizard steps. |
OnComplete | Action<WizardContext> | null | Callback fired when the wizard finishes successfully. |
OnCancel | Action<WizardContext> | null | Callback fired when the wizard is cancelled. |
OnStepChanging | Func<int,WizardContext,bool> | null | Called before each step transition; return false to cancel. |
OnStepChanged | Action<int,WizardContext> | null | Called after each step transition. |
OnProgress | Action<int,int,string> | null | Progress callback for long async operations (current, total, message). |
WizardStep
Each step in the Config.Steps list.
| Property | Type | Description |
|---|---|---|
Key | string | Unique key for this step (used in WizardContext). |
Title | string | Step title shown in the stepper / card UI. |
Description | string | Sub-title or hint shown under the step title. |
Icon | string | SVG/image icon path shown in the step indicator. |
Content | Control | The UserControl or Control displayed in the content panel for this step. |
State | StepState | Current state: Pending · Current · Completed · Error · Skipped. |
IsOptional | bool | Marks the step as optional (Skip button shows when AllowSkip=true). |
NextButtonText | string | Overrides Config.NextButtonText for this step only. |
BackButtonText | string | Overrides Config.BackButtonText for this step only. |
Validators | List<IWizardValidator> | Validators run automatically before navigating away. |
CanNavigateNext | Func<WizardContext,bool> | Delegate gate for next navigation. False = block with no error message. |
CanNavigateBack | Func<WizardContext,bool> | Delegate gate for back navigation. |
ShouldSkip | Func<WizardContext,bool> | If returns true at runtime, this step is automatically skipped. |
OnEnter | Action<WizardContext> | Sync callback when the step becomes active. |
OnLeave | Action<WizardContext> | Sync callback when the step is navigated away from. |
OnEnterAsync | Func<WizardContext,Task> | Async enter callback (e.g. load remote data). |
OnLeaveAsync | Func<WizardContext,Task> | Async leave callback (e.g. submit step data). |
Tag | object | Arbitrary attached data. |
WizardContext (Data Bus)
A typed key/value dictionary shared across all steps. The engine sets CurrentStepIndex and TotalSteps; your code reads/writes all other keys.
| Method / Property | Description |
|---|---|
SetValue(key, value) | Store any value under a string key. |
GetValue<T>(key, default) | Retrieve strongly-typed value; returns default if missing or unconvertable. |
ContainsKey(key) | Test whether a key has been set. |
Remove(key) | Delete a key. |
GetStepData(stepKey) | Per-step WizardStepData bag (isolated namespace per step). |
CurrentStepIndex | int — set by the engine during navigation. |
TotalSteps | int — set at construction time. |
StepValidation | Dictionary<int,bool> — engine writes true after each step validates. |
NavigationHistory | Stack<int> — push/pop order of visited step indices. |
Enumerations
StepState
| Value | Meaning |
|---|---|
Pending | Step has not been visited yet. |
Current | Step is currently active. |
Completed | Step was successfully passed through. |
Error | Step has unsatisfied validation errors. |
Skipped | Step was automatically bypassed via ShouldSkip. |
WizardResult
| Value | Meaning |
|---|---|
None | Wizard is still running. |
Completed | User clicked Finish and all validation passed. |
Cancelled | User clicked Cancel. |
Failed | An unhandled error occurred during completion. |
WizardManager (Entry Point)
WizardManager is a static class that acts as the single entry point for creation, display, and lifecycle management.
| Member | Type | Description |
|---|---|---|
DefaultStyle | WizardStyle | Global default style used when config doesn't specify one. Default: HorizontalStepper. |
EnableAnimations | bool | Enable/disable step-transition animations globally. Default: true. |
CreateWizard(config) | WizardInstance | Create a wizard without showing it. Use to hook events before display. |
ShowWizard(config) | DialogResult | Create and show as modal dialog; returns OK (Completed) or Cancel. |
ShowWizard(config, owner) | DialogResult | Same but with explicit owner window for correct centering. |
GetWizard(key) | WizardInstance | Retrieve a running wizard by its Key. |
CloseWizard(key) | void | Close a wizard by Key. |
CloseAllWizards() | void | Close all currently open wizard instances. |
WizardInstance Events
| Event | Args Type | Description |
|---|---|---|
StepChanging | StepChangingEventArgs | Raised before navigating away from the current step. Set e.Cancel = true to block navigation. |
StepChanged | StepChangedEventArgs | Raised after successfully navigating to a new step. Contains old and new step indices. |
Completed | WizardCompletedEventArgs | Raised when the user clicks Finish and all final validation passes. |
Cancelled | WizardCancelledEventArgs | Raised when the user clicks Cancel. |
WizardInstance navigation methods
| Method | Returns | Description |
|---|---|---|
NavigateNextAsync() | Task<bool> | Validate current step, fire events, advance to next step (skipping ShouldSkip steps). Returns false if blocked. |
NavigateBackAsync() | Task<bool> | Navigate to the previous step. Returns false if on first step or AllowBack=false. |
CompleteAsync() | Task<bool> | Validate final step, fire Completed event, fire OnComplete callback. |
CancelAsync() | Task | Fire Cancelled event and OnCancel callback, then close the form. |
CurrentStep | WizardStep | The currently active step. |
CurrentStepIndex | int | 0-based index of the active step. |
IsFirstStep | bool | True when on step 0. |
IsLastStep | bool | True when on the last step (Next becomes Finish). |
Result | WizardResult | Final result after the wizard closes. |
Context | WizardContext | The shared data bus. |
Config | WizardConfig | The construction configuration. |
Validation
Validators are attached to a WizardStep.Validators list. They implement IWizardValidator and are called automatically by the engine when Next / Finish is clicked. The first failing validator blocks navigation and shows the error inline.
Built-in Validators
| Class | What it checks |
|---|---|
RequiredFieldValidator(keys…) | Ensures the listed context keys exist and are non-empty. |
CustomValidator(func) | Full control — supply a Func<WizardContext, WizardStep, WizardValidationResult>. |
PredicateValidator(pred, msg) | Simple bool predicate. Fails with msg when predicate returns false. |
RegexValidator(key, pattern, msg) | Tests a context string value against a regex pattern. |
EmailValidator(key) | Validates that the context key contains a valid email address. |
RangeValidator(key, min, max) | Validates a numeric context value is within [min, max]. |
StringLengthValidator(key, min, max) | Validates string length. |
ValidationPatterns constants
ValidationPatterns.Email // ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
ValidationPatterns.Phone // ^\+?[\d\s\-()]{10,}$
ValidationPatterns.Url // ^https?://[\w\-]+(\.[\w\-]+)+[/#?]?.*$
ValidationPatterns.ZipCode // ^\d{5}(-\d{4})?$
ValidationPatterns.Integer // ^\-?\d+$
ValidationPatterns.Decimal // ^\-?\d+(\.\d+)?$
WizardValidationResult
// Factory methods — use these in custom validators
WizardValidationResult.Success()
WizardValidationResult.Error("Error message")
WizardValidationResult.Error("Error message", "contextKey") // focuses specific field
Delegate-based quick validation (no validator class needed)
new WizardStep
{
Key = "terms",
Title = "Terms",
CanNavigateNext = ctx =>
ctx.GetValue<bool>("termsAccepted", false),
}
Step Templates
Pre-built step UserControl classes that implement IWizardStepContent. Derive from WizardStepTemplateBase to get automatic OnStepEnter / OnStepLeave lifecycle and ValidationStateChanged event.
| Template Class | Purpose | Key Properties |
|---|---|---|
WelcomeStepTemplate | Introductory step with icon, title, and message text. | WelcomeTitle, WelcomeMessage, WelcomeIcon |
SummaryStepTemplate | Final review screen displaying collected data before Finish. | SummaryTitle, SummaryBuilder (Func<WizardContext, string>) |
Creating a custom step template
public class AccountStep : WizardStepTemplateBase
{
private TextBox _emailBox;
public AccountStep()
{
InitializeComponent(); // your designer code
}
protected override void LoadData()
{
// called on enter — populate UI from context
_emailBox.Text = Context.GetValue<string>("email", "");
}
protected override void SaveData()
{
// called on leave — persist UI back to context
Context.SetValue("email", _emailBox.Text.Trim());
}
public override WizardValidationResult Validate()
{
if (string.IsNullOrWhiteSpace(_emailBox.Text))
return WizardValidationResult.Error("Email is required", "email");
return WizardValidationResult.Success();
}
}
WizardFormFactory — Custom Styles
Register a factory function to provide a completely custom form for any WizardStyle value:
// Register a branded corporate wizard form
WizardFormFactory.RegisterFactory(
WizardStyle.Cards,
instance => new MyCorporateWizardForm(instance)
);
// Now all Cards style wizards use your custom form
WizardManager.ShowWizard(new WizardConfig
{
Style = WizardStyle.Cards,
// ...
});
Animations
WizardHelpers provides two built-in transitions used by the form hosts:
| Method | Effect | Details |
|---|---|---|
AnimateStepTransition(from, to, forward, onComplete) | Slide left/right | 12 frames at ~60 fps with ease-out cubic deceleration. Forward=true slides left (next), false slides right (back). |
AnimateFadeTransition(from, to, onComplete) | Cross-fade | Opacity blend between the old and new step controls. |
Disable all animations globally: WizardManager.EnableAnimations = false;
Quick Start
// Minimal 3-step wizard shown as modal dialog
var result = WizardManager.ShowWizard(new WizardConfig
{
Title = "New Connection",
Style = WizardStyle.HorizontalStepper,
AllowCancel = true,
Steps = new List<WizardStep>
{
new WizardStep
{
Key = "welcome",
Title = "Welcome",
Content = new WelcomeStepTemplate
{
WelcomeTitle = "New Connection Wizard",
WelcomeMessage = "This wizard will guide you through setting up a data connection."
}
},
new WizardStep
{
Key = "connection",
Title = "Connection Details",
Content = new ConnectionDetailsStep(), // your UserControl
Validators = new List<IWizardValidator>
{
new RequiredFieldValidator("serverName", "database"),
new RequiredFieldValidator("username")
}
},
new WizardStep
{
Key = "summary",
Title = "Summary",
Content = new SummaryStepTemplate
{
SummaryTitle = "Review Connection",
SummaryBuilder = ctx =>
$"Server: {ctx.GetValue<string>("serverName")}\n" +
$"Database: {ctx.GetValue<string>("database")}"
},
NextButtonText = "Finish"
}
},
OnComplete = ctx =>
{
var server = ctx.GetValue<string>("serverName");
var db = ctx.GetValue<string>("database");
CreateConnection(server, db);
}
}, this); // owner window
if (result == DialogResult.OK)
MessageBox.Show("Connection created!");
Advanced Examples
Hooking events before display
var instance = WizardManager.CreateWizard(new WizardConfig { /* ... */ });
instance.StepChanging += (s, e) =>
{
if (e.ToIndex == 2 && !PreFlightCheck())
{
e.Cancel = true;
MessageBox.Show("Please complete the pre-flight check first.");
}
};
instance.StepChanged += (s, e) =>
statusBar.Text = $"Step {e.ToIndex + 1} of {instance.Config.Steps.Count}";
instance.Completed += (s, e) =>
SaveWizardData(e.Context);
instance.ShowDialog(this);
Async step callbacks
new WizardStep
{
Key = "validate",
Title = "Validate Connection",
OnEnterAsync = async ctx =>
{
// runs when step becomes active — e.g. load defaults from API
var defaults = await ApiClient.GetConnectionDefaultsAsync();
ctx.SetValue("defaultServer", defaults.Server);
},
OnLeaveAsync = async ctx =>
{
// runs when navigating away — e.g. persist/validate server-side
var server = ctx.GetValue<string>("serverName");
bool ok = await ApiClient.TestConnectionAsync(server);
if (!ok)
throw new InvalidOperationException("Cannot reach server"); // blocks navigation
}
}
Conditional step skipping
new WizardStep
{
Key = "advancedOptions",
Title = "Advanced Options",
IsOptional = true,
ShouldSkip = ctx => !ctx.GetValue<bool>("showAdvanced", false),
Content = new AdvancedOptionsStep()
}
Custom CardStyle wizard with branded header
WizardFormFactory.RegisterFactory(WizardStyle.Cards, inst => new BrandedWizardForm(inst));
WizardManager.ShowWizard(new WizardConfig
{
Style = WizardStyle.Cards,
Title = "Onboarding",
Size = new Size(1000, 700),
AllowSkip = true,
ShowHelp = true,
HelpUrl = "https://docs.example.com/onboarding",
Steps = /* ... */
});
Reading collected data after completion
var instance = WizardManager.CreateWizard(config);
instance.ShowDialog(this);
if (instance.Result == WizardResult.Completed)
{
var ctx = instance.Context;
var name = ctx.GetValue<string>("firstName") + " " + ctx.GetValue<string>("lastName");
var plan = ctx.GetValue<string>("selectedPlan", "Free");
var accepted = ctx.GetValue<bool>("termsAccepted", false);
// process...
}
Best Practices
- Use WizardManager.ShowWizard() for fire-and-forget wizards. Use
CreateWizard()only when you need to hook events before display. - Store all cross-step data in WizardContext — do not use static fields or class-level variables. Context data is available in the
OnCompletecallback and inWizardInstance.Contextafter the dialog closes. - Prefer Step.Validators over CanNavigateNext delegates when you want inline error messages.
CanNavigateNextsilently blocks navigation without a user-facing message. - Use WizardStepTemplateBase for custom step controls — the
LoadData()/SaveData()pattern keeps concerns clean. - Mark optional steps with IsOptional=true and enable
AllowSkipin WizardConfig to give users an explicit Skip button. - Use ShouldSkip for programmatic conditional steps (e.g. skip billing step for Free plans). This is automatic and invisible to the user.
- Prefer OnEnterAsync / OnLeaveAsync for I/O (API calls, DB queries). The engine awaits the task before updating the UI.
- Disable animations during automated tests:
WizardManager.EnableAnimations = false; - Always pass an owner window to
ShowWizard(config, this)so the dialog centres correctly. - Register a custom factory once at startup in
WizardFormFactory.RegisterFactory()to globally change the UI for a given style.