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>

1 comment: