Tuesday, 5 June 2012

Silverlight Metro Style Wizard

Here is a quick example of a Metro style wizard in Silverlight 4.

This solution acts as a little prototype for laying out info within a wizard rather than a generic fully functional wizard control.  If that’s what you are after then I highly recommend the Shiny Wizard project on CodePlex.

So what can you find in this project:

  • An animation for the left hand side bar sliding in at the beginning
  • Examples of the Visual State Manager being used to set the visibility on various controls as the user steps through the wizard
  • Comments within the Xaml explaining what each code block does
  • An Image button control.  You only need to set the image and pressed image and it’s ready to go
  • An example of MVVM, with view models used for the main page and two of the wizard steps (Unique Identifiers and Currencies)
  • INotifyDataErrorInfo and an injectable validation rules engine (courtesy of Fluent Validation for .NET) for verifying the user input
  • BindVisualStateBehaviour class and converter that shows how to bind the Visual State Managers current visual state to a property in your view mode, from within your Xaml

It’s just a first draft and rough working prototype but if anyone would like to evolve the solution it can be found on github.

Here's the source:  https://github.com/stevenh77/MetroWizard

<UserControl x:Class="MetroWizard.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="clr-namespace:MetroWizard.Behaviors"
xmlns:c="clr-namespace:MetroWizard.Controls"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:converters="clr-namespace:MetroWizard.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:Views="clr-namespace:MetroWizard.Views"
x:Name="userControl"
mc:Ignorable="d">
<UserControl.Resources>
<Storyboard x:Name="OnLoad">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="gridSideBar" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)">
<EasingDoubleKeyFrame KeyTime="0" Value="-381" />
<EasingDoubleKeyFrame KeyTime="0:0:2" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<converters:ValueToStateNameConverter x:Key="ValueToStateNameConverter" />
</UserControl.Resources>

<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="400" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<!-- The following trigger is run when the window is first loaded -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded" SourceObject="{Binding ElementName=userControl}">
<ei:ControlStoryboardAction Storyboard="{StaticResource OnLoad}" />
</i:EventTrigger>
</i:Interaction.Triggers>

<!-- Binds the current VisualState to the ViewModel CurrentPage property -->
<i:Interaction.Behaviors>
<b:BindVisualStateBehavior StateName="{Binding CurrentPage, Converter={StaticResource ValueToStateNameConverter}}" />
</i:Interaction.Behaviors>

<!-- The CustomVisualStateManager keeps track of the current State for smooth sequential transitions -->
<!-- without it each state change would result in first returning to Step1 -->
<VisualStateManager.CustomVisualStateManager>
<ei:ExtendedVisualStateManager />
</VisualStateManager.CustomVisualStateManager>

<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="WizardStates" ei:ExtendedVisualStateManager.UseFluidLayout="True">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.5">
<VisualTransition.GeneratedEasingFunction>
<CircleEase EasingMode="EaseOut" />
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Page0">
<Storyboard>
<ColorAnimation Duration="0:0:0"
Storyboard.TargetName="IntroductionTextblock"
Storyboard.TargetProperty="(Control.Foreground).(SolidColorBrush.Color)"
To="{StaticResource MetroBlueDarkColor}" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="btnPrevious" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Page1">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="StepHighlighter" Storyboard.TargetProperty="(Grid.Row)">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Int32>1</System:Int32>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<Storyboard>
<ColorAnimation Duration="0:0:0"
Storyboard.TargetName="IdentifiersTextblock"
Storyboard.TargetProperty="(Control.Foreground).(SolidColorBrush.Color)"
To="{StaticResource MetroBlueDarkColor}" />
</Storyboard>
</Storyboard>
</VisualState>
<VisualState x:Name="Page2">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="StepHighlighter" Storyboard.TargetProperty="(Grid.Row)">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Int32>2</System:Int32>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ColorAnimation Duration="0:0:0"
Storyboard.TargetName="CurrenciesTextblock"
Storyboard.TargetProperty="(Control.Foreground).(SolidColorBrush.Color)"
To="{StaticResource MetroBlueDarkColor}" />
</Storyboard>
</VisualState>
<VisualState x:Name="Page3">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="StepHighlighter" Storyboard.TargetProperty="(Grid.Row)">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Int32>3</System:Int32>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ColorAnimation Duration="0:0:0"
Storyboard.TargetName="ReleaseDateTextblock"
Storyboard.TargetProperty="(Control.Foreground).(SolidColorBrush.Color)"
To="{StaticResource MetroBlueDarkColor}" />
</Storyboard>
</VisualState>
<VisualState x:Name="Page4">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="StepHighlighter" Storyboard.TargetProperty="(Grid.Row)">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Int32>4</System:Int32>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ColorAnimation Duration="0:0:0"
Storyboard.TargetName="FinishTextblock"
Storyboard.TargetProperty="(Control.Foreground).(SolidColorBrush.Color)"
To="{StaticResource MetroBlueDarkColor}" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="btnPrevious" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="btnNext" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="btnRestart" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

<!-- Top title bar -->
<Grid x:Name="gridTitleBar"
Grid.Column="0"
Grid.ColumnSpan="2"
Background="{StaticResource MetroBlueDarkBrush}">

<StackPanel Orientation="Horizontal">

<Viewbox Width="20"
Height="20"
Margin="10,0,0,0"
HorizontalAlignment="Left">
<Path Width="5"
Height="5"
Margin="0,0,0,0"
HorizontalAlignment="Left"
Data="M32.750164,30.46987C35.49025,32.299855 38.470455,33.919646 42.470594,34.389593 49.780787,35.249657 55.801014,31.809645 55.801014,31.809646L49.760765,52.698723C48.200775,53.468704 42.940582,55.758627 36.750303,55.038692 32.650185,54.558493 29.61004,52.908674 26.810015,51.048904z M17.245978,23.970247C17.959661,23.967486 18.699703,23.997397 19.46392,24.06677 23.604753,24.446759 26.635363,26.246706 29.555952,28.256649L23.614755,48.866045C23.084649,48.496056 18.063638,45.126154 13.442707,44.716166 6.2512589,44.066185 1.1802387,46.836103 0,47.546083L5.91119,27.076682C5.91119,27.076682,10.347044,23.996938,17.245978,23.970247z M39.610368,6.499918C42.360559,8.3300591 45.330765,9.9501858 49.331041,10.420223 56.641548,11.280289 62.661966,7.8400211 62.661966,7.8400211L56.631548,28.731647C55.06144,29.501707 49.801075,31.781884 43.610645,31.071829 39.510362,30.591792 36.470151,28.941663 33.669957,27.081518z M24.105657,0.00010490417C24.819157,-0.0020446777 25.559085,0.028625488 26.323275,0.098930359 30.473971,0.46888542 33.494477,2.2786732 36.414967,4.2884369L30.483973,24.896015C29.943882,24.526059 24.933043,21.156455 20.312268,20.736505 13.121064,20.09658 8.0402126,22.856255 6.8600159,23.576169L12.781007,3.1085758C12.781007,3.1085758,17.208497,0.020893097,24.105657,0.00010490417z"
Fill="{StaticResource MetroBlueLightBrush}"
Stretch="Uniform" />
</Viewbox>

<TextBlock x:Name="tbProductName"
Margin="10,0,0,0"
VerticalAlignment="Center"
FontSize="14"
Style="{StaticResource TextBlockStyle_TitleBar}"
Text="Global Sales Portal" />
</StackPanel>
</Grid>

<!-- Left hand side of the screen -->
<Grid x:Name="gridSideBar"
Grid.Row="1"
Grid.RowSpan="2"
Width="200"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Background="{StaticResource MetroBlueLightBrush}">

<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="35" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<!-- Allows the grid to be animated -->
<Grid.RenderTransform>
<CompositeTransform />
</Grid.RenderTransform>

<TextBlock Margin="20,0,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
FontSize="24"
Foreground="White"
Text="insert product" />


<Grid x:Name="gridWizardSteps" Grid.Row="4">

<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="35" />
<RowDefinition Height="35" />
<RowDefinition Height="35" />
<RowDefinition Height="35" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<Rectangle x:Name="StepHighlighter"
Grid.Row="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="White" />

<TextBlock x:Name="IntroductionTextblock"
Grid.Row="0"
Grid.Column="1"
Style="{StaticResource TextBlockStyle_SideBarWizardSteps}"
Text="INTRODUCTION" />

<TextBlock x:Name="IdentifiersTextblock"
Grid.Row="1"
Grid.Column="1"
Style="{StaticResource TextBlockStyle_SideBarWizardSteps}"
Text="UNIQUE IDENTIFIERS" />

<TextBlock x:Name="CurrenciesTextblock"
Grid.Row="2"
Grid.Column="1"
Style="{StaticResource TextBlockStyle_SideBarWizardSteps}"
Text="CURRENCIES" />

<TextBlock x:Name="ReleaseDateTextblock"
Grid.Row="3"
Grid.Column="1"
Style="{StaticResource TextBlockStyle_SideBarWizardSteps}"
Text="RELEASE DATE" />

<TextBlock x:Name="FinishTextblock"
Grid.Row="4"
Grid.Column="1"
Style="{StaticResource TextBlockStyle_SideBarWizardSteps}"
Text="FINISH" />
</Grid>

</Grid>


<!-- Right hand side of the page -->
<Grid x:Name="gridMainSection"
Grid.Row="1"
Grid.RowSpan="2"
Grid.Column="1"
HorizontalAlignment="Stretch"
Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="65" />
<RowDefinition Height="30" />
<RowDefinition Height="260" />
<RowDefinition />
</Grid.RowDefinitions>

<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="500" />
</Grid.ColumnDefinitions>

<TextBlock x:Name="tbWizardStepName"
Grid.Column="1"
VerticalAlignment="Bottom"
FontSize="36"
Foreground="{StaticResource MetroBlueDarkBrush}"
Text="{Binding SelectedItem.Header,
ElementName=tabControlWizardContent}" />

<TextBlock x:Name="textBlock"
Grid.Row="1"
Grid.Column="1"
Margin="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="12"
Foreground="{StaticResource MetroBlueDarkBrush}"
Text="{Binding SelectedItem.Tag,
ElementName=tabControlWizardContent}"
TextWrapping="Wrap" />

<controls:TabControl x:Name="tabControlWizardContent"
Grid.Row="2"
Grid.Column="1"
Margin="0,21,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="{x:Null}"
BorderBrush="{x:Null}"
SelectedIndex="{Binding CurrentPage,
Mode=TwoWay}">

<controls:TabItem Header="introduction"
Tag="WELCOME TO THE INSERT PRODUCT WIZARD"
Visibility="Collapsed" />

<controls:TabItem Header="unique identifiers"
Tag="FIRST, SET THE VALUES USED TO IDENTIFY THE GLOBAL PRODUCT."
Visibility="Collapsed">
<Views:IdentifiersView />
</controls:TabItem>
<controls:TabItem Header="currencies"
Tag="NEXT, ADD THE CURRENCIES IN WHICH THE PRODUCT TRADES."
Visibility="Collapsed">
<Views:CurrenciesView />
</controls:TabItem>
<controls:TabItem Header="release date"
Tag="FINALLY, SELECT THE RELEASE DATE."
Visibility="Collapsed">
<controls:DatePicker Width="175" Margin="0,14,0,0" />
</controls:TabItem>
<controls:TabItem Header="finish"
Tag="CONGRATULATIONS, THE PRODUCT HAS BEEN ADDED TO OUR CATALOG."
Visibility="Collapsed" />
</controls:TabControl>
</Grid>

<!-- Bottom App Bar -->
<Grid x:Name="gridAppBar"
Grid.Row="2"
Grid.ColumnSpan="2"
Background="{StaticResource MetroBlueDarkBrush}">

<Grid.ColumnDefinitions>
<ColumnDefinition Width="400" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="8" />
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<c:ImageButton x:Name="btnPrevious"
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Right"
Command="{Binding PreviousCommand}"
Image="Images/Previous.png"
PressedImage="Images/Previous.png"
Text="PREVIOUS" />
<c:ImageButton x:Name="btnNext"
Grid.Row="1"
Grid.Column="2"
HorizontalAlignment="Center"
Command="{Binding NextCommand}"
Image="Images/Next.png"
PressedImage="Images/Next.png"
Text="NEXT" />

<c:ImageButton x:Name="btnRestart"
Grid.Row="1"
Grid.Column="3"
HorizontalAlignment="Center"
Command="{Binding FinishCommand}"
Image="Images/Restart.png"
PressedImage="Images/Restart.png"
Text="RESTART"
Visibility="Collapsed" />
</Grid>
</Grid>
</UserControl>

3 comments:

  1. And how to Create a WF C# Metro Style Wizard ??

    ReplyDelete
  2. Nice Article. Is it possible to create tabitem with multiple hyperlinks and when you click on any link it should navigate to another screen. Issue am facing here is how to navigate to second screen from first and if we use contentframes or navigateurl when you click on previous it goes to wrong screen. Please help.

    ReplyDelete
  3. Hi Steven, thank you very much for this nice article,
    then i need your help about this, can you help me how to create the same layout with WPF

    ReplyDelete