State Machines and Wizard Components: A Clean Implementation Approach

State Machines and Wizard Components: A Clean Implementation Approach

This past week, I have been working on a prototype for a wizard component. As you might know, in computer interfaces, wizard components (or multi-step forms) allow users to navigate through a finite number of steps or pages until they reach the end. Wizards are particularly useful because they don’t overwhelm users with too many choices at once, effectively minimizing the number of decisions a user needs to make at any specific moment.

The current prototype is created using XAF from DevExpress. If you follow this blog, you probably know that I’m a DevExpress MVP, and I wanted to use their tools to create this prototype.

I’ve built wizard components before, but mostly in a rush. Those previous implementations had the wizard logic hardcoded directly inside the UI components, with no separation between the UI and the underlying logic. While they worked, they were quite messy. This time, I wanted to take a more structured approach to creating a wizard component, so here are a few of my findings. Most of this might seem obvious, but sometimes it’s hard to see the forest for the trees when you’re sitting in front of the computer writing code.

Understanding the Core Concept: State Machines

To create an effective wizard component, you need to understand several underlying concepts. The idea of a wizard is actually rooted in system theory and computer science—it’s essentially an implementation of what’s called a state machine or finite state machine.

Theory of a State Machine

A state machine is the same as a finite state machine (FSM). Both terms refer to a computational model that describes a system existing in one of a finite number of states at any given time.

A state machine (or FSM) consists of:

  • States: Distinct conditions the system can be in
  • Transitions: Rules for moving between states
  • Events/Inputs: Triggers that cause transitions
  • Actions: Operations performed when entering/exiting states or during transitions

The term “finite” emphasizes that there’s a limited, countable number of possible states. This finite nature is crucial as it makes the system predictable and analyzable.

State machines come in several variants:

  • Deterministic FSMs (one transition per input)
  • Non-deterministic FSMs (multiple possible transitions per input)
  • Mealy machines (outputs depend on state and input)
  • Moore machines (outputs depend only on state)

They’re widely used in software development, hardware design, linguistics, and many other fields because they make complex behavior easier to visualize, implement, and debug. Common examples include traffic lights, UI workflows, network protocols, and parsers.

In practical usage, when someone refers to a “state machine,” they’re almost always talking about a finite state machine.

Implementing a Wizard State Machine

Here’s an implementation of a wizard state machine that separates the logic from the UI:

public class WizardStateMachineBase
{
    readonly List<WizardPage> _pages;
    int _currentIndex;

    public WizardStateMachineBase(IEnumerable<WizardPage> pages)
    {
        _pages = pages.OrderBy(p => p.Index).ToList();
        _currentIndex = 0;
    }

    public event EventHandler<StateTransitionEventArgs> StateTransition;

    public WizardPage CurrentPage => _pages[_currentIndex];

    public virtual bool MoveNext()
    {
        if (_currentIndex < _pages.Count - 1) { var args = new StateTransitionEventArgs(CurrentPage, _pages[_currentIndex + 1]); OnStateTransition(args); if (!args.Cancel) { _currentIndex++; return true; } } return false; } public virtual bool MovePrevious() { if (_currentIndex > 0)
        {
            var args = new StateTransitionEventArgs(CurrentPage, _pages[_currentIndex - 1]);
            OnStateTransition(args);

            if (!args.Cancel)
            {
                _currentIndex--;
                return true;
            }
        }
        return false;
    }

    protected virtual void OnStateTransition(StateTransitionEventArgs e)
    {
        StateTransition?.Invoke(this, e);
    }
}

public class StateTransitionEventArgs : EventArgs
{
    public WizardPage CurrentPage { get; }
    public WizardPage NextPage { get; }
    public bool Cancel { get; set; }

    public StateTransitionEventArgs(WizardPage currentPage, WizardPage nextPage)
    {
        CurrentPage = currentPage;
        NextPage = nextPage;
        Cancel = false;
    }
}

public class WizardPage
{
    public int Index { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public bool IsRequired { get; set; } = true;
    public bool IsCompleted { get; set; }
    
    // Additional properties specific to your wizard implementation
    public object Content { get; set; }
    
    public WizardPage(int index, string title)
    {
        Index = index;
        Title = title;
    }
    
    public virtual bool Validate()
    {
        // Default implementation assumes page is valid
        // Override this method in derived classes to provide specific validation logic
        return true;
    }
}

Benefits of This Approach

As you can see, by defining a state machine, you significantly narrow down the implementation possibilities. You solve the problem of “too many parts to consider” – questions like “How do I start?”, “How do I control the state?”, “Should the state be in the UI or a separate class?”, and so on. These problems can become really complicated, especially if you don’t centralize the state control.

This simple implementation of a wizard state machine shows how to centralize control of the component’s state. By separating the state management from the UI components, we create a cleaner, more maintainable architecture.

The WizardStateMachineBase class manages the collection of pages and handles navigation between them, while the StateTransitionEventArgs class provides a mechanism to cancel transitions if needed (for example, if validation fails). The newly added WizardPage class encapsulates all the information needed for each step in the wizard.

What’s Next?

The next step will be to control how the visual components react to the state of the machine – essentially connecting our state machine to the UI layer. This will include handling the display of the current page content, updating navigation buttons (previous/next/finish), and possibly showing progress indicators. I’ll cover this UI integration in my next post.

By following this pattern, you can create wizard interfaces that are not only user-friendly but also maintainable and extensible from a development perspective.

Source Code

egarim/WizardStateMachineTest

About US

YouTube

https://www.youtube.com/c/JocheOjedaXAFXAMARINC

Our sites

https://www.bitframeworks.com

https://www.xari.io

https://www.xafers.training

Let’s discuss your XAF Support needs together! This 1-hour call/zoom will give you the opportunity to define the roadblocks in your current XAF solution

Schedule a meeting with us on this link

Guide to Blazor Component Design and Implementation for backend devs

Guide to Blazor Component Design and Implementation for backend devs

Over time, I transitioned to using the first versions of my beloved framework, XAF. As you might know, XAF generates a polished and functional UI out of the box. Using XAF made me more of a backend developer since most of the development work wasn’t visual—especially in the early versions, where the model designer was rudimentary (it’s much better now).

Eventually, I moved on to developing .NET libraries and NuGet packages, diving deep into SOLID design principles. Fun fact: I actually learned about SOLID from DevExpress TV. Yes, there was a time before YouTube when DevExpress posted videos on technical tasks!

Nowadays, I feel confident creating and publishing my own libraries as NuGet packages. However, my “old monster” was still lurking in the shadows: UI components. I finally decided it was time to conquer it, but first, I needed to choose a platform. Here were my options:

  1. Windows Forms: A robust and mature platform but limited to desktop applications.
  2. WPF: A great option with some excellent UI frameworks that I love, but it still feels a bit “Windows Forms-ish” to me.
  3. Xamarin/Maui: I’m a big fan of Xamarin Forms and Xamarin/Maui XAML, but they’re primarily focused on device-specific applications.
  4. Blazor: This was the clear winner because it allows me to create desktop applications using Electron, embed components into Windows Forms, or even integrate with MAUI.

Recently, I’ve been helping my brother with a project in Blazor. (He’s not a programmer, but I am.) This gave me an opportunity to experiment with design patterns to get the most out of my components, which started as plain HTML5 pages.

Without further ado, here are the key insights I’ve gained so far.

Building high-quality Blazor components requires attention to both the C# implementation and Razor markup patterns. This guide combines architectural best practices with practical implementation patterns to create robust, reusable components.

1. Component Architecture and Organization

Parameter Organization

Start by organizing parameters into logical groups for better maintainability:

public class CustomForm : ComponentBase
{
    // Layout Parameters
    [Parameter] public string Width { get; set; }
    [Parameter] public string Margin { get; set; }
    [Parameter] public string Padding { get; set; }
    
    // Validation Parameters
    [Parameter] public bool EnableValidation { get; set; }
    [Parameter] public string ValidationMessage { get; set; }
    
    // Event Callbacks
    [Parameter] public EventCallback<bool> OnValidationComplete { get; set; }
    [Parameter] public EventCallback<string> OnSubmit { get; set; }
}

Corresponding Razor Template

<div class="form-container" style="width: @Width; margin: @Margin; padding: @Padding">
    <form @onsubmit="HandleSubmit">
        @if (EnableValidation)
        {
            <div class="validation-message">
                @ValidationMessage
            </div>
        }
        @ChildContent
    </form>
</div>

2. Smart Default Values and Template Composition

Component Implementation

public class DataTable<T> : ComponentBase
{
    [Parameter] public int PageSize { get; set; } = 10;
    [Parameter] public bool ShowPagination { get; set; } = true;
    [Parameter] public string EmptyMessage { get; set; } = "No data available";
    [Parameter] public IEnumerable<T> Items { get; set; } = Array.Empty<T>();
    [Parameter] public RenderFragment HeaderTemplate { get; set; }
    [Parameter] public RenderFragment<T> RowTemplate { get; set; }
    [Parameter] public RenderFragment FooterTemplate { get; set; }
}

Razor Implementation

<div class="table-container">
    @if (HeaderTemplate != null)
    {
        <header class="table-header">
            @HeaderTemplate
        </header>
    }
    
    <div class="table-content">
        @if (!Items.Any())
        {
            <div class="empty-state">@EmptyMessage</div>
        }
        else
        {
            @foreach (var item in Items)
            {
                @RowTemplate(item)
            }
        }
    </div>
    
    @if (ShowPagination)
    {
        <div class="pagination">
            <!-- Pagination implementation -->
        </div>
    }
</div>

3. Accessibility and Unique IDs

Component Implementation

public class FormField : ComponentBase
{
    private string fieldId = $"field-{Guid.NewGuid():N}";
    private string labelId = $"label-{Guid.NewGuid():N}";
    private string errorId = $"error-{Guid.NewGuid():N}";
    
    [Parameter] public string Label { get; set; }
    [Parameter] public string Error { get; set; }
    [Parameter] public bool Required { get; set; }
}

Razor Implementation

<div class="form-field">
    <label id="@labelId" for="@fieldId">
        @Label
        @if (Required)
        {
            <span class="required" aria-label="required">*</span>
        }
    </label>
    
    <input id="@fieldId" 
           aria-labelledby="@labelId"
           aria-describedby="@errorId"
           aria-required="@Required" />
    
    @if (!string.IsNullOrEmpty(Error))
    {
        <div id="@errorId" class="error-message" role="alert">
            @Error
        </div>
    }
</div>

4. Virtualization and Performance

Component Implementation

public class VirtualizedList<T> : ComponentBase
{
    [Parameter] public IEnumerable<T> Items { get; set; }
    [Parameter] public RenderFragment<T> ItemTemplate { get; set; }
    [Parameter] public int ItemHeight { get; set; } = 50;
    [Parameter] public Func<ItemsProviderRequest, ValueTask<ItemsProviderResult<T>>> ItemsProvider { get; set; }
}

Razor Implementation

<div class="virtualized-container" style="height: 500px; overflow-y: auto;">
    <Virtualize Items="@Items"
                ItemSize="@ItemHeight"
                ItemsProvider="@ItemsProvider"
                Context="item">
        <ItemContent>
            <div class="list-item" style="height: @(ItemHeight)px">
                @ItemTemplate(item)
            </div>
        </ItemContent>
        <Placeholder>
            <div class="loading-placeholder" style="height: @(ItemHeight)px">
                <div class="loading-animation"></div>
            </div>
        </Placeholder>
    </Virtualize>
</div>

Best Practices Summary

1. Parameter Organization

  • Group related parameters with clear comments
  • Provide meaningful default values
  • Use parameter validation where appropriate

2. Template Composition

  • Use RenderFragment for customizable sections
  • Provide default templates when needed
  • Enable granular control over component appearance

3. Accessibility

  • Generate unique IDs for form elements
  • Include proper ARIA attributes
  • Support keyboard navigation

4. Performance

  • Implement virtualization for large datasets
  • Use loading states and placeholders
  • Optimize rendering with appropriate conditions

Conclusion

Building effective Blazor components requires attention to both the C# implementation and Razor markup. By following these patterns and practices, you can create components that are:

  • Highly reusable
  • Performant
  • Accessible
  • Easy to maintain
  • Flexible for different use cases

Remember to adapt these practices to your specific needs while maintaining clean component design principles.