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
ShouldSkipdefaults to() => false— all pages are shown unless explicitly configured- If all remaining pages are skipped,
CanMoveForwardreturnsfalseandCanFinishreturnstrue, presenting the Finish button - Skip conditions apply equally to forward and backward navigation
Wizard Example