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:
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.
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
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:
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.
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
Install the platform-specific module:
Install-Package 'Llamachant.ExpressApp.Wizard.Blazor'
OR
Install-Package 'Llamachant.ExpressApp.Wizard.Win'
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();
});
Install the platform-specific module:
Install-Package 'Llamachant.ExpressApp.Wizard.Blazor'
OR
Install-Package 'Llamachant.ExpressApp.Wizard.Win'
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();
});
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 |
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 |
Each wizard view can be assigned a Description, Id, Index, Name, and the View you want the page to display.
The Description property only works in Blazor, or in Wizard 97 mode in WinForms
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 |
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 |
Each wizard view can be assigned a Description, Id, Index, Name, and the View you want the page to display.
The Description property only works in Blazor, or in Wizard 97 mode in WinForms
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:
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 |
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
}
}
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:
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 |
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
}
}
Skip wizard pages conditionally based on runtime state using the ShouldSkip predicate on WizardPageDetails.
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.
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;
}
}
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;
}
}
}
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();
ShouldSkip defaults to () => false — all pages are shown unless explicitly configuredCanMoveForward returns false and CanFinish returns true, presenting the Finish buttonSkip wizard pages conditionally based on runtime state using the ShouldSkip predicate on WizardPageDetails.
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.
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;
}
}
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;
}
}
}
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();
ShouldSkip defaults to () => false — all pages are shown unless explicitly configuredCanMoveForward returns false and CanFinish returns true, presenting the Finish button