Wizards

NuGet Package Details

Package Name
Llamachant.ExpressApp.Wizard
Blazor Package
Llamachant.ExpressApp.Wizard.Blazor
WinForms Package
Llamachant.ExpressApp.Wizard.Win

Overview

The Wizard Module allows you to present multi-step workflows using a sequence of detail views, effectively turning object editing into a guided wizard experience. This enhances usability by breaking complex forms into manageable pages, making data entry clearer and more intuitive.

You can configure the wizard to appear:

  • Only on object creation (e.g. when using the New action), or
  • Every time an object is opened, replacing the default detail view

Each step in the wizard is a standard detail view defined in the Model Editor. These views are then assigned as wizard pages within a parent detail view using model configuration. This approach gives you full design-time control over wizard flow and layout without needing custom UI code.

TIP

Ensure all view variants are removed for any view that is used as a wizard page as this can cause unexpected behavior in the wizard

Installation

Step 1: Install the Module

Install the platform-specific module:

Install-Package 'Llamachant.ExpressApp.Wizard.Blazor'

OR

Install-Package 'Llamachant.ExpressApp.Wizard.Win'

Step 2: Register the Module

Register the platform-specific module (Blazor or Winforms):

public override void Setup(XafApplication application) {
    base.Setup(application);
    // Blazor:
    this.RequiredModuleTypes.Add(typeof(LlamachantFrameworkWizardModuleBlazor));
    // WinForms:
    this.RequiredModuleTypes.Add(typeof(LlamachantFrameworkWizardModuleWin));
}

OR

services.AddXaf(Configuration, builder => {
    builder.UseApplication<ExpressAppBlazorApplication>();
    builder.Modules
        // Blazor:
        .AddLlamachantFrameworkWizardModuleBlazor();
        // WinForms:
        .AddLlamachantFrameworkWizardModuleWin();
});

Setup

Step 1:

Most of the Wizard module setup is done in the Model. To start, create detail views to represent the pages or steps of your wizard (these can be clones of your parent detail view with the layout changed).

For example:

Application Llamachant Framework Values
Views
Llamachant.ExpressApp
BusinessObject
BusinessObject_DetailView ShowInWizard Never / New Only / Always
BusinessObject_DetailView_Page1
BusinessObject_DetailView_Page2

Step 2:

Once your views are created, add them to the Wizard node on the parent detail view, then set the parent detail view's ShowInWizard property to either NewOnly or Always.
For example:

Application Llamachant Framework Values
Views
Llamachant.ExpressApp
BusinessObject
BusinessObject_DetailView ShowInWizard Never / New Only / Always
WizardStyle WizardStyle.WizardAero / WizardStyle.Wizard97
Wizard WizardTitle String
Page1
Page2 Description String
Name String
ThrowValidationExceptions True / False
View View Node - Example: BusinessObject_DetailView_Page2

Step 3:

Each wizard view can be assigned a Description, Id, Index, Name, and the View you want the page to display.

TIP

The Description property only works in Blazor, or in Wizard 97 mode in WinForms.

Show a Wizard Using an Action

Sometimes you may not want to show a wizard directly from the New action. You may instead want the option to only display the wizard when an action is executed. In order to do this, follow these steps:

Step 1:

If you want your action to display a wizard, but you don't want the New action to display a wizard, use a different Detail View as your parent view (If you're okay having both functions in place, skip to step 2).

For example:

Application Llamachant Framework Values
Views
Llamachant.ExpressApp
BusinessObject
BusinessObject_DetailView_Wizard ShowInWizard Never
Wizard WizardTitle String
Page1
Page2
BusinessObject_DetailView_Page1
BusinessObject_DetailView_Page2

Step 2:

Set up your controller to display your parent detail view. This can be set up the same way you would set up any other controller. Then, in your CustomizePopupWindowParams event, set Context of the CustomizePopupWindowParamsEventArgs equal to LlamachantFrameworkWizardModule.WIZARDCONTEXT.

For example:

public class ClientWizardController : ViewController
{
    public PopupWindowShowAction CreateClient { get; set; }
    public ClientWizardController()
    {
        CreateClient = new PopupWindowShowAction(this, nameof(CreateClient), DevExpress.Persistent.Base.PredefinedCategory.Edit);
        CreateClient.CustomizePopupWindowParams += CreateClient_CustomizePopupWindowParams;
        CreateClient.Execute += CreateClient_Execute;
    }
    private void CreateClient_CustomizePopupWindowParams(object sender, CustomizePopupWindowParamsEventArgs e)
    {
        IObjectSpace space = ObjectSpace.CreateNestedObjectSpace();
        Client client = space.CreateObject<Client>();
        DetailView dv = Application.CreateDetailView(space, "Client_DetailView_Wizard", false, client);
        // Setting the context to this Wizard Context will make the popup window use the wizard instead of the regular view
        e.Context = LlamachantFrameworkWizardModule.WIZARDCONTEXT;
        e.View = dv;
    }
    private void CreateClient_Execute(object sender, PopupWindowShowActionExecuteEventArgs e)
    {
        // Perform any logic on your created object here
    }
}

Wizard Page Skipping

Skip wizard pages conditionally based on runtime state using the ShouldSkip predicate on WizardPageDetails.

How It Works

Each WizardPageDetails has a ShouldSkip property of type Func<bool>. When it returns true, the navigator skips that page during forward and backward navigation. The predicate is evaluated at navigation time, so changes to the underlying data are reflected immediately without re-wiring.

CanMoveForward, CanMoveBackward, and CanFinish all respect skip conditions, so button states update automatically.

Basic Usage

Subscribe to the WizardInitialized event and set the ShouldSkip predicate on the desired pages:

public class MyWizardController : ViewController<DetailView>
{
    protected override void OnActivated()
    {
        base.OnActivated();
        var wizard = Frame.GetController<WizardController>();
        wizard.WizardInitialized += Wizard_Initialized;
    }

protected override void OnDeactivated()
    {
        var wizard = Frame.GetController<WizardController>();
        wizard.WizardInitialized -= Wizard_Initialized;
        base.OnDeactivated();
    }

private void Wizard_Initialized(object sender, WizardInitializedEventArgs e)
    {
        var addressPage = e.WizardTemplate.WizardNavigator.Pages
            .FirstOrDefault(p => p.WizardView.Name == "AddressStep");

if (addressPage != null)
            addressPage.ShouldSkip = () => !((MyObject)View.CurrentObject).RequiresAddress;
    }
}

Skipping Multiple Pages

private void Wizard_Initialized(object sender, WizardInitializedEventArgs e)
{
    var navigator = e.WizardTemplate.WizardNavigator;

foreach (var page in navigator.Pages)
    {
        switch (page.WizardView.Name)
        {
            case "AddressStep":
                page.ShouldSkip = () => !((MyObject)View.CurrentObject).RequiresAddress;
                break;
            case "PaymentStep":
                page.ShouldSkip = () => ((MyObject)View.CurrentObject).Total == 0;
                break;
        }
    }
}

Refreshing Button State After Data Changes

If a user changes data on the current page that affects skip conditions (e.g., checking a box that makes the current page the last non-skipped page), the Next/Back/Finish button states won't update until the next navigation. To force an immediate refresh in Blazor, call RefreshActions():

Frame.GetController<BlazorWizardDialogController>()?.RefreshActions();

Notes

  • ShouldSkip defaults to () => false — all pages are shown unless explicitly configured
  • If all remaining pages are skipped, CanMoveForward returns false and CanFinish returns true, presenting the Finish button
  • Skip conditions apply equally to forward and backward navigation

Wizard Example