Skip to content

Stepper

Philipp Spiegel edited this page May 6, 2018 · 13 revisions

Stepper

Steppers are handy to break a big task into smaller parts. These are so called steps. It displays the progress of the full task as a numbered sequence of steps.

There a two layouts for a stepper, namely horizontal and vertical. Additionally the stepper can be in linear mode. This means that the user can only edit the steps in their fixed order. A non-linear Stepper allows the user to edit the steps in any order. You will find details and usage scenarios in the Material Design specification.

API

Stepper

Property Description
Layout Defines the stepper as either horizontal or vertical.
IsLinear Enables the linear mode by disabling the buttons of the header.
Steps The steps which will be shown inside the stepper.
ActiveStep The active step. (read-only)
BlockNavigationOnValidationErrors Specifies whether validation errors will block the navigation or not.
Event Description
ActiveStepChanged An event raised by changing to active step.
ContinueNavigation An event raised by navigating to the next step in a linear order.
BackNavigation An event raised by navigating to the previous step in a linear order.
StepNavigation An event raised by navigating to an arbitrarystep in a non-linear stepper.
CancelNavigation An event raised by cancelling the process.
StepValidation An event raised by starting the validation of an step.
Command Description
ActiveStepChangedCommand A command called by changing the active step.
ContinueNavigationCommand A command called by navigating to the next step in a linear order.
BackNavigationCommand A command called by navigating to the previous step in a linear order.
StepNavigationCommand A command called by navigating to an arbitrary step in a non-linear stepper.
CancelNavigationCommand A command called by cancelling the process.
StepValidationCommand A command called by starting the validation of an step.

IStep

IStep is the basic interface for representing a step in a Stepper. Step does implement it.

Property Description
Header The header of the step.
Content The content of the step.
HasValidationErrors True, if this step is in an invalid semantic state.
Method Description
Validate Validates this step.

StepTitleHeader

StepTitleHeader realizes a nice text header with first and second level titles as shown on the screenshots. The corresponding data template is already implemented and will be automatically applied.

Property Description
FirstLevelTitle The text of the first level title. A value of null will hide this title.
SecondLevelTitle The text of the second level title beneath the first level title. A value of null will hide the this title. It uses a smaller font size.

StepButtonBar

StepButtonBar is a convenience control for the navigation buttons inside a Stepper. It features buttons as shown on the bottom of screenshots. Setting the content for a button will show it. Otherwise the button will be collapsed.

Property Description
Continue The content for the continue button.
Back The content for the back button.
Cancel The content for the cancel button.

Screenshots

Code example

We begin by defining some view models for the content of the steps:

public class StepperTutorialOneViewModel
{
    public StepperTutorialOneViewModel() { }
}

public class StepperTutorialTwoViewModel
{
    public StepperTutorialTwoViewModel() { }
}

public class StepperTutorialThreeViewModel
{
    public StepperTutorialThreeViewModel() { }
}

public class StepperTutorialFourViewModel
{
    public StepperTutorialFourViewModel() { }
}

Next we create a property for the list of steps. Our stepper will bind to it. The steps are required to implement the IStep interface. The class Step already implements the interface, so we will use it. If you like to implement some custom validation logic, you may create your own sub class to override the Validate() method.

public List<IStep> Steps
{
    get
    {
        return new List<IStep>()
        {
            new Step() { Header = new StepTitleHeader() { FirstLevelTitle = "What is a Stepper?" }, Content = new StepperTutorialOneViewModel() },
            new Step() { Header = new StepTitleHeader() { FirstLevelTitle = "Layout and navigation" }, Content = new StepperTutorialTwoViewModel() },
            new Step() { Header = new StepTitleHeader() { FirstLevelTitle = "Steps", SecondLevelTitle = "Header and content" }, Content = new StepperTutorialThreeViewModel() },
            new Step() { Header = new StepTitleHeader() { FirstLevelTitle = "Validation" }, Content = new StepperTutorialFourViewModel() }
        };
    }
}

Thirdly, we need some data templates for the content. Have a look at the StepButtonBar inside each data template. It creates the navigation buttons at the bottom of the steps:

<!-- import namespaces -->
xmlns:viewModel="clr-namespace:MaterialDesignExtensionsDemo.ViewModel"
xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"

<!-- define the data templates -->
<DataTemplate DataType="{x:Type viewModel:StepperTutorialOneViewModel}">
    <StackPanel Orientation="Vertical">
        <TextBlock Text="Steppers are handy to break a big task into smaller parts. These are so called steps. It displays the progress of the full task as a numbered sequence of steps." TextWrapping="WrapWithOverflow" />
        <controls:StepButtonBar Continue="CONTINUE" Cancel="CANCEL" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:StepperTutorialTwoViewModel}">
    <StackPanel Orientation="Vertical">
        <TextBlock Text="There a two layouts for a Stepper, namely horizontal and vertical. It can be set by using the Layout property." TextWrapping="WrapWithOverflow" />
        <TextBlock Margin="0,16,0,0" Text="Additionally the Stepper will be in linear mode by setting the IsLinear property to true. This means that the user can only edit the steps in their fixed order. A non-linear Stepper allows the user to edit the steps in any order." TextWrapping="WrapWithOverflow" />
        <TextBlock Margin="0,16,0,0" Text="The basic navigation inside a stepper will be accomplished by using simple back and continue buttons. They allow the user to browse through the steps in their order. Just use the StepButtonBar like this tutorial to avoid the nasty reimplementation on your own. The user may also switch the steps of a non-linear Stepper by clicking on the headers. " TextWrapping="WrapWithOverflow" />
        <controls:StepButtonBar Back="BACK" Continue="CONTINUE" Cancel="CANCEL" />
    </StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:StepperTutorialThreeViewModel}">
    <StackPanel Orientation="Vertical">
        <TextBlock Text="The Stepper uses the steps out of its Steps property. This property expects a collection of IStep objects. The Step class already implements this basic interface for a step." TextWrapping="WrapWithOverflow" />
        <TextBlock Margin="0,16,0,0" Text="A step consists out of a header and a content. Consider to define one view model class per step content and a DataTemplate for it (just the everyday MVVM magic). The header may contain arbitrary content. If you like the header of this tutorial, just use the StepTitleHeader class." TextWrapping="WrapWithOverflow" />
        <controls:StepButtonBar Back="BACK" Continue="CONTINUE" Cancel="CANCEL" />
    </StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:StepperTutorialFourViewModel}">
    <StackPanel Orientation="Vertical">
        <TextBlock Text="Each time the user switches to another step, an optional validation phase for the until now active step will be started by the Stepper. The Validate() method of the step will be called first. Afterwards the StepValidation event will be raised followed by the call of the StepValidationCommand command." TextWrapping="WrapWithOverflow" />
        <TextBlock Margin="0,16,0,0" Text="The different validation handlers may set the HasValidationErrors property of the step to indicate input errors. If the BlockNavigationOnValidationErrors property of the Stepper is true, the Stepper blocks the navigation as long as the active step has invalid data." TextWrapping="WrapWithOverflow" />
        <controls:StepButtonBar Back="BACK" Continue="FINISH" Cancel="CANCEL" />
    </StackPanel>
</DataTemplate>

Finally, we can create the Stepper:

<controls:Stepper IsLinear="False" Layout="Horizontal" Steps="{Binding Path=Steps, Mode=OneTime}" />

You can define the steps inside the XAML alternatively, if you do not like the code behind:

<!-- we also need the namespace of Step next to the others defined by the previous snippet -->
xmlns:model="clr-namespace:MaterialDesignExtensions.Model;assembly=MaterialDesignExtensions"

<!-- assuming the data templates are available -->

<controls:Stepper IsLinear="False" Layout="Horizontal">
    <model:Step>
        <model:Step.Header>
            <model:StepTitleHeader FirstLevelTitle="What is a Stepper?" />
        </model:Step.Header>
        <model:Step.Content>
            <viewModel:StepperTutorialOneViewModel />
        </model:Step.Content>
    </model:Step>
    <model:Step>
        <model:Step.Header>
            <model:StepTitleHeader FirstLevelTitle="Layout and navigation" />
        </model:Step.Header>
        <model:Step.Content>
            <viewModel:StepperTutorialTwoViewModel />
        </model:Step.Content>
    </model:Step>
    <model:Step>
        <model:Step.Header>
            <model:StepTitleHeader FirstLevelTitle="Steps" SecondLevelTitle="Header and content" />
        </model:Step.Header>
        <model:Step.Content>
            <viewModel:StepperTutorialThreeViewModel />
        </model:Step.Content>
    </model:Step>
    <model:Step>
        <model:Step.Header>
            <model:StepTitleHeader FirstLevelTitle="Validation" />
        </model:Step.Header>
        <model:Step.Content>
            <viewModel:StepperTutorialFourViewModel />
        </model:Step.Content>
    </model:Step>
</controls:Stepper>

Validation

The Stepper supports different ways to run custom validation logic for its steps. The validation procedure will be triggered right before any navigation. The validation logic will set the HasValidationErrors property of the steo, if it detects some invalid invalid. According to the BlockNavigationOnValidationErrors property the Stepper may cancel the navigation as long as the step has validation errors.

Sub class of IStep

You can implement validation logic for each step by implementing your own class by inheriting from IStep or Step. The you can override the Validate() method.

public class MyStepOne : Step
{
    public MyStepOne() : base() { }
    
    public override void Validate()
    {
        // place your validation logic here
        //     assuming we have a input field for the birthday of the person and want to check the input
        bool ageOk = CheckAgeOfPerson();
        
        // if the age is not OK, we set a validation error
        if (ageOk)
        {
            HasValidationErrors = false;
        }
        else
        {
            HasValidationErrors = true;
        }
    }
}

Register for the event or command

The Stepper has a StepValidation event and a StepValidationCommand command. You can register your custom event handler or command to hook up validation. The step to validate will be assigned to the arguments of them.

public class MyControl : UserControl
{
    // define a command
    public static readonly MyValidationCommand = new RoutedCommand();
    
    public MyControl()
        : base()
    {
        CommandBindings.Add(new CommandBinding(MyValidationCommand, StepValidationCommandHandler));
    }
    
    // define an event handler
    private void StepValidationHandler(object sender, StepValidationEventArgs args)
    {
        // call some validation logic
        args.Step.HasValidationErrors = !ValidateTheStep(args.Step);
    }

    private void StepValidationCommandHandler(object sender, ExecutedRoutedEventArgs args)
    {
        // call some validation logic
        args.Step.HasValidationErrors = !ValidateTheStep(args.Parameter as IStep);
    }
    
    // the validation method
    private bool ValidateTheStep(IStep step)
    {
        // do some validation and return true or false
        return true;
    }
}
<!-- register the event handler via XAML -->
<controls:Stepper IsLinear="False" Layout="Horizontal" Steps="{Binding Path=Steps, Mode=OneTime}"
                  StepValidation="StepValidationHandler"
                  StepValidationCommand="{x:static local:MyControl.MyValidationCommand}" />