Overview

Each wizard form type is paired with a corresponding painter class that handles all rendering concerns. Painters are obtained through the WizardPainterFactory, which maps WizardStyle enum values to painter implementations. Painters receive theme tokens, layout bounds, and step state data from their host form and render accordingly.

PainterStyleRegistration KeyRenders
VerticalStepperPainterVerticalStepper"VerticalStepper"Left sidebar, numbered circles with checkmarks, vertical connector lines, active/completed/upcoming step states
HorizontalStepperPainterHorizontalStepper"HorizontalStepper"Top bar with connected step nodes, horizontal progress line, filled/outlined circle states
MinimalPainterMinimal"Minimal"No step indicators — only navigation button bar with Back/Next/Cancel
CardsPainterCards"Cards"Card surface with drop shadow, step dot indicators, card enter/exit animation rendering
public static class WizardPainterFactory
{
    private static readonly Dictionary<WizardStyle, Func<IWizardPainter>> _factories = new()
    {
        [WizardStyle.VerticalStepper] = () => new VerticalStepperPainter(),
        [WizardStyle.HorizontalStepper] = () => new HorizontalStepperPainter(),
        [WizardStyle.Minimal] = () => new MinimalPainter(),
        [WizardStyle.Cards] = () => new CardsPainter()
    };

    public static IWizardPainter Create(WizardStyle style)
    {
        if (_factories.TryGetValue(style, out var factory))
            return factory();
        return new VerticalStepperPainter(); // fallback
    }

    public static void Register(WizardStyle style, Func<IWizardPainter> factory)
    {
        _factories[style] = factory;
    }
}

IWizardPainter Contract

All wizard painters implement the IWizardPainter interface. The host form calls these methods in order: Initialize once at form creation, then PaintStepIndicator, PaintConnector, and PaintNavigation on each Paint cycle. The painter receives bounds and state data via properties set by the form before painting.

public interface IWizardPainter
{
    void Initialize(WizardForm form, IBeepTheme theme);
    void PaintStepIndicator(Graphics g, int stepIndex, WizardStepState state, Rectangle bounds);
    void PaintConnector(Graphics g, int fromStepIndex, int toStepIndex, WizardStepState fromState,
        Rectangle bounds);
    void PaintNavigation(Graphics g, WizardNavigationState navState, Rectangle bounds);
    void PaintBackground(Graphics g, Rectangle bounds);
}
MethodWhen CalledPurpose
Initialize(WizardForm, IBeepTheme)Form constructorStores references to host form and theme. Prepares any reusable brushes, pens, fonts. Called once per form lifetime.
PaintStepIndicator(Graphics, int, WizardStepState, Rectangle)OnPaint, per stepRenders a single step indicator: circle with number, checkmark if completed, highlighted if active, dimmed if upcoming. stepIndex is 0-based. state is Completed/Active/Upcoming/Error.
PaintConnector(Graphics, int, int, WizardStepState, Rectangle)OnPaint, between stepsRenders the connector line between step indicators fromStepIndex and toStepIndex. The fromState determines if the line is solid (completed) or dashed (upcoming).
PaintNavigation(Graphics, WizardNavigationState, Rectangle)OnPaint, nav barRenders navigation button bar with Back/Next/Cancel buttons respecting the current WizardNavigationState flags.
PaintBackground(Graphics, Rectangle)OnPaint, first callRenders the entire background of the indicator/step area. Called before individual step paints.

The WizardStepState enum defines the visual state of each step indicator:

public enum WizardStepState
{
    Upcoming,    // Not yet reached — gray, dimmed
    Active,      // Current step — theme accent color, bold
    Completed,   // Completed successfully — green/checkmark
    Error        // Completed with error — red/warning
}

VerticalStepperPainter

Paints the left-side vertical stepper panel. Step indicators are rendered as numbered circles (stepIndex + 1) connected by vertical lines. Completed steps display a checkmark icon instead of the number. The active step uses the theme accent color with a bolder border stroke.

AspectValueSource
Circle radius18pxVerticalStepperLayoutManager.StepCircleRadius
Vertical spacing56px between centersVerticalStepperLayoutManager.StepVerticalSpacing
Line width3pxConstant
Completed circle filltheme.StepperCompleteColorIBeepTheme
Active circle bordertheme.StepperActiveColor, 3pxIBeepTheme
Upcoming circle stroketheme.StepperUpcomingColor, 2pxIBeepTheme
Completed lineSolid, theme.StepperCompleteColorIBeepTheme
Upcoming lineDashed, theme.StepperUpcomingColorIBeepTheme
Fonttheme.DefaultFont, 10pt, Bold for activeIBeepTheme
public class VerticalStepperPainter : IWizardPainter
{
    private WizardForm _form;
    private IBeepTheme _theme;

    public void Initialize(WizardForm form, IBeepTheme theme)
    {
        _form = form;
        _theme = theme;
    }

    public void PaintStepIndicator(Graphics g, int stepIndex, WizardStepState state, Rectangle bounds)
    {
        var center = new Point(bounds.X + bounds.Width / 2, bounds.Y + bounds.Height / 2);
        int radius = 18;

        using (var circleBrush = GetCircleBrush(state))
        using (var circlePen = GetCirclePen(state))
        using (var textBrush = GetTextBrush(state))
        using (var font = GetStepFont(state))
        {
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            g.FillEllipse(circleBrush, center.X - radius, center.Y - radius, radius * 2, radius * 2);
            g.DrawEllipse(circlePen, center.X - radius, center.Y - radius, radius * 2, radius * 2);

            if (state == WizardStepState.Completed)
            {
                DrawCheckmark(g, center, radius - 4);
            }
            else
            {
                string num = (stepIndex + 1).ToString();
                var size = g.MeasureString(num, font);
                g.DrawString(num, font, textBrush, center.X - size.Width / 2, center.Y - size.Height / 2);
            }
        }

        // Draw step title label below the circle
        if (stepIndex < _form.Config.Steps.Count)
        {
            var label = _form.Config.Steps[stepIndex].Title;
            var labelBounds = new Rectangle(bounds.X, bounds.Y + radius * 2 + 8,
                bounds.Width, bounds.Height - radius * 2 - 8);
            using (var labelFont = state == WizardStepState.Active
                ? new Font(_theme.DefaultFont, FontStyle.Bold) : new Font(_theme.DefaultFont, FontStyle.Regular))
            {
                var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Near };
                g.DrawString(label, labelFont, Brushes.Gray, labelBounds, sf);
            }
        }
    }
    // ...
}

HorizontalStepperPainter

Paints a top-bar horizontal step indicator. Steps are rendered as connected nodes: filled circles for completed, outlined with accent fill for active, and gray outlines for upcoming. A continuous horizontal line runs behind the circles. The line is solid and accent-colored up to the active step, then dashed and gray beyond it.

AspectValueDescription
Circle radius16pxStep node circle size
Line positionCenter Y of step rowRuns through circle centers
Line thickness4pxHorizontal progress bar
Node spacingEqual distributionCalculated as barWidth / (stepCount + 1)
Completed filltheme.StepperCompleteColorSolid filled circle
Active filltheme.Background (white)White circle with accent border
Active bordertheme.StepperActiveColor, 3pxThick accent stroke
Label positionBelow each circleStep title centered under node
Completed lineSolid, theme accent, 4pxFrom left edge to active step center
Upcoming lineDashed, theme.StepperUpcomingColor, 2pxFrom active step center to right edge
public class HorizontalStepperPainter : IWizardPainter
{
    public void PaintConnector(Graphics g, int fromStepIndex, int toStepIndex,
        WizardStepState fromState, Rectangle bounds)
    {
        // The full horizontal line is painted once in PaintBackground,
        // segmented by state: completed=solid/accent, upcoming=dashed/gray
        var layout = (_form.LayoutManager as HorizontalStepperLayoutManager);
        if (layout == null) return;

        var centers = layout.StepNodeCenters;
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

        int lineY = centers[0].Y;
        using (var completedPen = new Pen(_theme.StepperCompleteColor, 4))
        using (var upcomingPen = new Pen(_theme.StepperUpcomingColor, 2)
               { DashStyle = System.Drawing.Drawing2D.DashStyle.Dash })
        {
            // Draw completed line up to active step
            int activeIdx = _form.Config.CurrentStepIndex;
            if (activeIdx > 0)
                g.DrawLine(completedPen, centers[0].X, lineY, centers[activeIdx].X, lineY);

            // Draw upcoming line from active step to end
            if (activeIdx < centers.Length - 1)
                g.DrawLine(upcomingPen, centers[activeIdx].X, lineY,
                    centers[centers.Length - 1].X, lineY);
        }
    }

    public void PaintStepIndicator(Graphics g, int stepIndex, WizardStepState state, Rectangle bounds)
    {
        var center = new Point(bounds.X + bounds.Width / 2, bounds.Y + bounds.Height / 2);
        int radius = 16;

        using (var fill = GetNodeBrush(state))
        using (var border = GetNodePen(state))
        {
            if (state == WizardStepState.Completed)
                g.FillEllipse(fill, center.X - radius, center.Y - radius, radius * 2, radius * 2);
            else
                g.FillEllipse(Brushes.White, center.X - radius, center.Y - radius, radius * 2, radius * 2);

            g.DrawEllipse(border, center.X - radius, center.Y - radius, radius * 2, radius * 2);

            if (state == WizardStepState.Completed)
                DrawCheckmark(g, center, 8);
            else
            {
                string num = (stepIndex + 1).ToString();
                DrawCenteredText(g, num, state == WizardStepState.Active
                    ? _theme.StepperActiveColor : _theme.StepperUpcomingColor, center);
            }
        }
    }
}

MinimalPainter

The Minimal painter renders no step indicators — only the navigation button bar. It provides the cleanest possible wizard experience, suitable for simple 2-3 step flows where step indicators add unnecessary visual noise. The nav bar is painted with theme-consistent colors for Back, Next/Cancel buttons.

public class MinimalPainter : IWizardPainter
{
    public void PaintStepIndicator(Graphics g, int stepIndex, WizardStepState state, Rectangle bounds)
    {
        // No step indicators rendered — intentional no-op
    }

    public void PaintConnector(Graphics g, int fromStepIndex, int toStepIndex,
        WizardStepState fromState, Rectangle bounds)
    {
        // No connectors — intentional no-op
    }

    public void PaintNavigation(Graphics g, WizardNavigationState navState, Rectangle bounds)
    {
        // Draw border separating nav bar from content
        using (var borderPen = new Pen(_theme.BorderColor))
            g.DrawLine(borderPen, bounds.X, bounds.Y, bounds.X + bounds.Width, bounds.Y);

        // Navigation buttons are actual WinForms buttons positioned by the layout manager.
        // The painter only renders the bar background and separator line.
        using (var bgBrush = new SolidBrush(_theme.StatusBarBackColor))
            g.FillRectangle(bgBrush, bounds);
    }

    public void PaintBackground(Graphics g, Rectangle bounds)
    {
        using (var bgBrush = new SolidBrush(_theme.BackgroundColor))
            g.FillRectangle(bgBrush, bounds);
    }
}
Painted ElementColor Source
Form backgroundtheme.BackgroundColor
Nav bar backgroundtheme.StatusBarBackColor
Nav bar separator linetheme.BorderColor, 1px solid
Back button (WinForms)theme.ButtonBackColor / theme.ButtonBorderColor
Next/Finish button (WinForms)theme.ButtonBackColor / theme.ButtonBorderColor (PrimaryButton variant)
Cancel button (WinForms)theme.ButtonBackColor (ghost/outline style)

CardsPainter

Paints card-based wizard transitions. The card surface is a rounded rectangle with a drop shadow. Step position is indicated by dot indicators below the card. The painter works with the animation engine to render enter/exit animations: slide, fade, or flip transitions.

AspectValueSource
Card corner radiustheme.BorderRadius (typically 8–12px)IBeepTheme
Card backgroundtheme.CardBackColor (or theme.BackgroundColor)IBeepTheme
Card bordertheme.CardBorderColor, 1pxIBeepTheme
Drop shadow4px blur, 2px offset, 30% opacity blackHard-coded graphics path
Dot size10px diameterConstant
Dot spacing16px center-to-centerConstant
Active dot colortheme.StepperActiveColorIBeepTheme
Completed dot colortheme.StepperCompleteColorIBeepTheme
Upcoming dot colortheme.StepperUpcomingColorIBeepTheme
Transition typesSlideRight, SlideLeft, Fade, FlipHorizontal, FlipVerticalWizardAnimationType enum
public class CardsPainter : IWizardPainter
{
    private WizardForm _form;
    private IBeepTheme _theme;
    private GraphicsPath _cardPath;
    private GraphicsPath _shadowPath;

    public void PaintBackground(Graphics g, Rectangle bounds)
    {
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

        // Draw drop shadow (using shadow path offset by 2px)
        using (var shadowBrush = new SolidBrush(Color.FromArgb(77, 0, 0, 0)))
        {
            var shadowBounds = new Rectangle(bounds.X + 2, bounds.Y + 2,
                bounds.Width - 4, bounds.Height - 4);
            _shadowPath = GetRoundedRect(shadowBounds, _theme.BorderRadius);
            g.FillPath(shadowBrush, _shadowPath);
        }

        // Draw card surface
        var cardBounds = new Rectangle(bounds.X, bounds.Y,
            bounds.Width - 4, bounds.Height - 4);
        _cardPath = GetRoundedRect(cardBounds, _theme.BorderRadius);

        using (var cardBrush = new SolidBrush(_theme.CardBackColor))
        using (var cardPen = new Pen(_theme.CardBorderColor))
        {
            g.FillPath(cardBrush, _cardPath);
            g.DrawPath(cardPen, _cardPath);
        }

        // Draw step dots below card
        DrawStepDots(g, cardBounds);
    }

    private void DrawStepDots(Graphics g, Rectangle cardBounds)
    {
        int dotY = cardBounds.Bottom + 24;
        int totalSteps = _form.Config.Steps.Count;
        int totalWidth = totalSteps * 10 + (totalSteps - 1) * 6;
        int startX = cardBounds.X + (cardBounds.Width - totalWidth) / 2;

        for (int i = 0; i < totalSteps; i++)
        {
            int x = startX + i * 16;
            var state = GetStepState(i);
            Color dotColor = state switch
            {
                WizardStepState.Completed => _theme.StepperCompleteColor,
                WizardStepState.Active => _theme.StepperActiveColor,
                _ => _theme.StepperUpcomingColor
            };

            using (var dotBrush = new SolidBrush(dotColor))
                g.FillEllipse(dotBrush, x, dotY, 10, 10);
        }
    }

    private GraphicsPath GetRoundedRect(Rectangle rect, int radius)
    {
        var path = new GraphicsPath();
        int d = radius * 2;
        path.AddArc(rect.X, rect.Y, d, d, 180, 90);
        path.AddArc(rect.Right - d, rect.Y, d, d, 270, 90);
        path.AddArc(rect.Right - d, rect.Bottom - d, d, d, 0, 90);
        path.AddArc(rect.X, rect.Bottom - d, d, d, 90, 90);
        path.CloseFigure();
        return path;
    }
}

Card transition animation types supported by the engine:

Animation TypeEnum ValueVisual Effect
Slide to the rightSlideRightNew card slides in from right, old slides out left
Slide to the leftSlideLeftNew card slides in from left, old slides out right
Cross-fadeFadeOld card fades out, new card fades in simultaneously (opacity lerp)
Flip horizontalFlipHorizontalCard rotates 180° on Y axis — back face shows old content, front face shows new
Flip verticalFlipVerticalCard rotates 180° on X axis

Examples

// Style selection — the factory creates the right painter automatically
var config = new WizardConfig
{
    Style = WizardStyle.VerticalStepper,
    // ...steps, etc.
};
// Internally: painter = WizardPainterFactory.Create(WizardStyle.VerticalStepper)
// which returns new VerticalStepperPainter()

// Change style programmatically
config.Style = WizardStyle.HorizontalStepper;
// Now uses HorizontalStepperPainter

// Register a custom painter
WizardPainterFactory.Register(WizardStyle.VerticalStepper, () => new CustomStepperPainter());
// All VerticalStepper wizards now use CustomStepperPainter

// Custom painter example — subclass and override rendering
public class CustomStepperPainter : VerticalStepperPainter
{
    protected override void DrawCheckmark(Graphics g, Point center, int size)
    {
        // Custom checkmark with star instead
        using (var pen = new Pen(Color.Gold, 3))
        {
            DrawStar(g, pen, center, size);
        }
    }
}

// Access painter for direct manipulation
var form = WizardManager.GetWizard("my-wizard").FormHost as VerticalStepperWizardForm;
var painter = form.Painter as VerticalStepperPainter;
// painter now accessible for custom tweaks (rarely needed)