Saturday, 30 June 2012

Async Task.Delay

Internal code for Task.Delay (.NET 4.5)

public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken)
{
if (millisecondsDelay < -1)
{
throw new ArgumentOutOfRangeException("millisecondsDelay", Environment.GetResourceString("Task_Delay_InvalidMillisecondsDelay"));
}
if (cancellationToken.IsCancellationRequested)
{
return FromCancellation(cancellationToken);
}
if (millisecondsDelay == 0)
{
return CompletedTask;
}
DelayPromise state = new DelayPromise(cancellationToken);
if (cancellationToken.CanBeCanceled)
{
state.Registration = cancellationToken.InternalRegisterWithoutEC(delegate (object state) {
((DelayPromise) state).Complete();
}, state);
}
if (millisecondsDelay != -1)
{
state.Timer = new Timer(delegate (object state) {
((DelayPromise) state).Complete();
}, state, millisecondsDelay, -1);
state.Timer.KeepRootedWhileScheduled();
}
return state;
}

The following examples were taken from samples within LinqPad 4.


How to implement Task.Delay in 4.0

/* You can write Task-based asynchronous methods by utilizing a TaskCompletionSource.
A TaskCompletionSource gives you a 'slave' Task that you can manually signal.
Calling SetResult() signals the task as complete, and any continuations kick off. */

void Main()
{
for (int i = 0; i < 10000; i++)
{
Task task = Delay (2000);
task.ContinueWith (_ => "Done".Dump());
}
}

Task Delay (int milliseconds) // Asynchronous NON-BLOCKING method
{
var tcs = new TaskCompletionSource<object>();
new Timer (_ => tcs.SetResult (null)).Change (milliseconds, -1);
return tcs.Task;
}

How NOT to implement Task.Delay 4.0

/* Instead of using a TaskCompletionSource, you can get (seemingly) the same result by
calling Task.Factory.StartNew. This method runs a delegate on a pooled thread,
which is fine for compute-bound work. However, it's not great for I/O-bound work
because you tie up a thread, blocking for the duration of the operation! */

void Main()
{
for (int i = 0; i < 10000; i++)
{
Task task = Delay (2000);
task.ContinueWith (_ => "Done".Dump());
}
}

Task Delay (int milliseconds) // Asynchronous non-blocking wrapper....
{
return Task.Factory.StartNew (() =>
{
Thread.Sleep (2000); // ... around a BLOCKING method!
});
}

// This approach is correct for COMPUTE-BOUND operations.

Returning a Value

// There's also a generic subclass of Task called Task<TResult>. This has a Result
// property which stores a RETURN VALUE of the concurrent operation.

void Main()
{
Task<int> task = GetAnswerToLifeUniverseAndEverything();
task.ContinueWith (_ => task.Result.Dump());
}

Task<int> GetAnswerToLifeUniverseAndEverything () // We're now returning a Task of int
{
var tcs = new TaskCompletionSource<int>(); // Call SetResult with a int instead:
new Timer (_ => tcs.SetResult (42)).Change (3000, -1);
return tcs.Task;
}

// This is great, because most methods return a value of some sort!
//
// You can think of Task<int> as 'an int in the future'.

Dealing with Exceptions

// Tasks also have an Exception property for storing the ERROR should a concurrent 
// operation fail. Calling SetException on a TaskCompletionSource signals as complete
// while populating its Exception property instead of the Result property.

void Main()
{
Task<int> task = GetAnswerToLifeUniverseAndEverything();
task.ContinueWith (_ => task.Exception.InnerException.Dump());
}

Task<int> GetAnswerToLifeUniverseAndEverything()
{
var tcs = new TaskCompletionSource<int>();
new Timer (_ => tcs.SetException (new Exception ("You're not going to like the answer!"))).Change (2000, -1);
return tcs.Task;
}

Example Usage


// Kick off a download operation in the background:
Task<string> task = new WebClient().DownloadStringTaskAsync (new Uri ("http://www.albahari.com/threading"));

// Call the .ContinueWith method to tell a task to do something when it's finished.
task.ContinueWith (_ =>
{
if (task.Exception != null)
task.Exception.InnerException.Dump();
else
{
string html = task.Result;
html.Dump();
}
});

Thursday, 28 June 2012

LeanUX

Bill Scott has some interest learning's on LeanUX that he’s sharing with the community:

Worth keeping an eye on his blog or follow him on twitter.

http://looksgoodworkswell.blogspot.co.uk/2012/06/anti-patterns-for-lean-ux.html

Wednesday, 27 June 2012

Claims based Security .NET 4.5

In .NET 4.5 Microsoft have moved Claims based security into MSCorLib a central part of the .NET framework.

This move away from explicit Role based security to a Claims based model promotes a finer grain approach and a decoupling of the security rules from the application into a third party service.

Old Style Permissions

Attribute Based

[PrincipalPermission(SecurityAction.Demand, Role = "Development")]
private void DoDeveloperWork()
{
Console.WriteLine("You are a developer");
}


Inline Security

try
{
new PrincipalPermission(null, "Development").Demand();
Console.WriteLine("You are a developer");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}


Claims Based Permissions

using System;
using System.IdentityModel.Services;
using System.Security;
using System.Security.Claims;
using System.Security.Permissions;
using System.Threading;

namespace ClaimsBasedSecurityDemo
{
class CustomAuthorisationManagerExample
{
public void Execute()
{
PrintHeader();
Setup();

try
{
TestPermissionsSuccess();
TestPermissionsFail();
}
catch (SecurityException e)
{
Console.WriteLine(": " + e.Message);
}
}

[ClaimsPrincipalPermission(SecurityAction.Demand,
Operation = "Add",
Resource = "Customer")]
private void TestPermissionsSuccess()
{
Console.WriteLine("Code successfully runs!");
}

[ClaimsPrincipalPermission(SecurityAction.Demand,
Operation = "Delete",
Resource = "Customer")]
private void TestPermissionsFail()
{
Console.WriteLine("We do not get here...");
}


private void Setup()
{
var myClaim = new Claim("http://myclaims/customer", "add");
var currentIdentity = new CorpIdentity("stevenh", myClaim);
var principal = new ClaimsPrincipal(currentIdentity);
Thread.CurrentPrincipal = principal;
}

private void PrintHeader()
{
Console.WriteLine("Custom Authorisation Manager Examples");
Console.WriteLine("_____________________________________");
Console.WriteLine();
}
}
}

Custom ClaimsAuthorizationManager

using System.Security.Claims;
using System.Linq;

namespace ClaimsBasedSecurityDemo
{
class AuthorisationManager : ClaimsAuthorizationManager
{
public override bool CheckAccess(AuthorizationContext context)
{
var resource = context.Resource.First().Value;
var action = context.Action.First().Value;

// hardcoded rules could be replaced by injection or load from xml
if (resource == "Customer" && action == "Add")
{
var hasAccess = context.Principal.HasClaim("http://myclaims/customer", "add");
return hasAccess;
}

return false;
}
}
}


app.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="system.identityModel"
type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.identityModel>
<identityConfiguration>
<claimsAuthorizationManager type="ClaimsBasedSecurityDemo.AuthorisationManager, ClaimsBasedSecurityDemo" />
</identityConfiguration>
</system.identityModel>
</configuration>

The source code below gives examples of .NET security in various forms.


image


Source


https://github.com/stevenh77/ClaimsBasedSecurityDemo

Sunday, 24 June 2012

Silverlight Xbox Menu

Here is an example of the Xbox menu in Silverlight. 

This prototype lets you move between the menu sections by clicking on the menu headings bing, home, social, videos and settings.

The bing screen and metro tiles don’t fire any events, it’s just to show the look and feel and animations between states and on selection of the tiles.

Source

https://github.com/stevenh77/SilverlightXboxMenu

Updated version

This latest version contains a background image and fixes for window resizes.

Link to online demo

http://stevenhollidge.com/blog-source-code/silverlightxboxmenu2/

Source

https://github.com/stevenh77/SilverlightXboxMenu2

Saturday, 23 June 2012

Setting EventTriggers in Blend4

In Blend 3 triggers used to have its own menu which was then replaced in Blend 4 by properties on a behavior.

Because it’s easy to forget here’s a quick screenshot of how to set the event trigger:

image

Assets menu > Behaviors > Add the behaviour > Set the Properties on the behavior

Here is an example of a custom control deriving from control to set a visual state based on an event trigger.

The “SelectionBorder” has its border brush set to null but when then mouse enters it gets set to white.  Mouse leaving event sets it back to it's normal state in the Visual State Manager.

To show a contrast in styles there is also a Storyboard resource activated by the MouseLeftButtonDown event that makes the tile look as though it's been pushed down.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:SilverlightXboxMenu.Controls"
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"
mc:Ignorable="d">

<Style TargetType="controls:MetroTile">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:MetroTile">
<Border x:Name="SelectionBorder"
Margin="1"
BorderBrush="{x:Null}"
BorderThickness="3"
Cursor="Hand"
RenderTransformOrigin="0.5,0.5">
<Border.Resources>
<Storyboard x:Name="ButtonPressStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SelectionBorder" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.GlobalOffsetZ)">
<EasingDoubleKeyFrame KeyTime="0" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="-50">
<EasingDoubleKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Border.Resources>
<Border.Projection>
<PlaneProjection />
</Border.Projection>
<Border.RenderTransform>
<CompositeTransform />
</Border.RenderTransform>

<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseDown" />
<VisualState x:Name="MouseOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SelectionBorder" Storyboard.TargetProperty="(Border.BorderBrush)">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{StaticResource MetroWhiteBrush}" />
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation d:IsOptimized="True"
Duration="0"
Storyboard.TargetName="SelectionBorder"
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)"
To="1.03" />
<DoubleAnimation d:IsOptimized="True"
Duration="0"
Storyboard.TargetName="SelectionBorder"
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)"
To="1.03" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="MainContainer" Background="{StaticResource MetroGreenBrush}">

<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter" SourceObject="{Binding ElementName=SelectionBorder}">
<ei:GoToStateAction StateName="MouseOver" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave" SourceObject="{Binding ElementName=SelectionBorder}">
<ei:GoToStateAction StateName="Normal" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeftButtonDown">
<ei:ControlStoryboardAction Storyboard="{StaticResource ButtonPressStoryboard}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid x:Name="MainGrid" Background="{Binding Background, RelativeSource={RelativeSource TemplatedParent}}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>

<Image x:Name="PART_DISPLAY_ICON"
Grid.Row="0"
Margin="10,10,10,2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="{Binding DisplayIcon,
RelativeSource={RelativeSource TemplatedParent}}"
Stretch="None" />

<TextBlock x:Name="PART_DISPLAY_TITLE_CONTAINER"
Grid.Row="1"
Margin="10,2,10,10"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
FontFamily="/SilverlightXboxMenu;component/Fonts/Fonts.zip#Segoe UI"
FontSize="16"
Foreground="{StaticResource MetroWhiteBrush}"
Text="{Binding DisplayText,
RelativeSource={RelativeSource TemplatedParent}}"
TextWrapping="Wrap" />
</Grid>
</Border>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

Another option would have been to derive from button to get visual states up and running then edit from there.


Live Demo


Here’s a live demo, hover over and click the tiles to see the different states:


Source


https://github.com/stevenh77/SilverlightXboxMenu

Friday, 22 June 2012

Silverlight Metro Clock

To mark my moving to Switzerland and starting a new job here is a Windows 8 Metro style clock in Silverlight 4.

Bear in mind this is just to demonstrate layout and design, the clock updates every minute but not necessarily on the minute so it doesn’t run like clockwork.  Plus the wi-fi and battery icons are just for show and don’t dynamically update with actual readings.

Source

DigitalClock.xaml.cs

using System;
using System.Windows.Threading;

namespace SilverlightMetroClock
{
public partial class DigitalClock
{

public DigitalClock()
{
InitializeComponent();

// updates every minute but perhaps not on the minute :)
DispatcherTimer timer = new DispatcherTimer {Interval = new TimeSpan(0, 0, 1, 0)};
timer.Tick += timer_Tick;
timer.Start();

UpdateDisplay();
}

void timer_Tick(object sender, EventArgs e)
{
UpdateDisplay();
}

void UpdateDisplay()
{
var now = DateTime.Now;

TimeTextBlock.Text = now.ToString("HH:mm");
DayTextBlock.Text = now.ToString("dddd");
DateTextBlock.Text = now.ToString("MMMM d");
}
}
}


DigitalClock.xaml

<UserControl x:Class="SilverlightMetroClock.DigitalClock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="500"
Height="150">

<Grid Background="{StaticResource BackgroundBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width="25" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<Viewbox x:Name="WifiIcon"
Grid.Row="0"
Grid.Column="1"
Height="25"
Margin="0,0,0,5"
VerticalAlignment="Bottom">
<Path Width="26"
Height="26"
Margin="0,0,0,0"
Data="M33.172937,38.270306C33.636298,38.265931 34.098408,38.31531 34.548637,38.417819 33.938327,39.957959 33.588148,41.638111 33.588148,43.398272 33.588148,45.628473 34.138429,47.718663 35.088913,49.578833 32.867781,50.43891 30.286466,49.918863 28.64563,48.058694 26.524549,45.658476 26.804692,41.968142 29.255941,39.777943 30.381515,38.780352 31.782854,38.283432 33.172937,38.270306z M42.515223,36.582736C41.955198,36.582736 41.395174,36.79527 40.970165,37.22034 40.120026,38.070478 40.120026,39.460438 40.970165,40.310577L44.15025,43.490663 41.150105,46.490808C40.300089,47.340947 40.300089,48.730907 41.150105,49.581045 42.000122,50.430941 43.390204,50.430941 44.240344,49.581045L47.240488,46.5809 50.12051,49.460922C50.980659,50.311062 52.360731,50.311062 53.210749,49.460922 54.060764,48.611028 54.070774,47.220823 53.210749,46.370685L50.330604,43.490663 53.390688,40.430457C54.240828,39.580561 54.240828,38.190357 53.390688,37.340463 52.540672,36.490324 51.150589,36.480314 50.300572,37.340463L47.240488,40.400425 44.06028,37.22034C43.635272,36.79527,43.075247,36.582736,42.515223,36.582736z M47.180427,32.67007C53.110767,32.670071 57.910927,37.470352 57.910927,43.400571 57.910927,49.331034 53.110767,54.131071 47.180427,54.131071 41.260096,54.131071 36.449927,49.331034 36.449927,43.400571 36.449927,37.470352 41.260096,32.670071 47.180427,32.67007z M33.018136,20.500918C36.1167,20.47427 39.221307,21.069399 42.202856,22.325673 46.113247,24.105708 49.633599,27.405772 53.063942,31.165845 51.283764,30.305828 49.293565,29.815819 47.183354,29.815819 44.04304,29.815819 41.15275,30.89584 38.85252,32.685874 33.091944,29.145806 28.101444,30.305828 20.820716,36.945957L20.400673,37.315964 13.629995,29.655815 14.030037,29.285808C19.4137,23.627574,26.201293,20.559546,33.018136,20.500918z M34.071325,0.0013465881C39.261796,0.043453217 44.356957,1.0763702 48.876303,3.4138107 57.485651,8.124321 62.65526,12.764826 66.664957,17.205307L58.505575,24.436091C54.71586,20.265638 50.416186,16.30521 44.456637,13.274881 32.057576,7.4142456 15.908798,12.43479 7.4394379,21.795804L7.0194702,22.185847 0,14.244986 0.42996979,13.854944C6.9197903,6.2569313,20.806787,-0.10626984,34.071325,0.0013465881z"
Fill="{StaticResource ForegroundBrush}"
Stretch="Uniform" />
</Viewbox>

<Viewbox x:Name="BatteryIcon"
Grid.Row="1"
Grid.Column="1"
Height="25"
Margin="0,5,0,0"
VerticalAlignment="Top">
<Path Width="26"
Height="26"
Margin="0,0,0,0"
Data="M5.0491318,48.393315C4.3017105,48.393315,3.6969174,48.522218,3.6969172,48.682416L3.6969172,58.229268C3.6969174,58.389474,4.3017105,58.519574,5.0491318,58.519574L21.312653,58.519574C22.059346,58.519574,22.663838,58.389474,22.663838,58.229268L22.663838,48.682416C22.663838,48.522218,22.059346,48.393315,21.312653,48.393315z M5.0491318,34.039033C4.3017105,34.039033,3.6969174,34.167933,3.6969172,34.329535L3.6969172,43.876489C3.6969174,44.035188,4.3017105,44.166688,5.0491318,44.166688L21.312653,44.166688C22.059346,44.166688,22.663838,44.035188,22.663838,43.876489L22.663838,34.329535C22.663838,34.167933,22.059346,34.039033,21.312653,34.039033z M5.0491318,19.686151C4.3017105,19.686151,3.6969174,19.815052,3.6969172,19.975152L3.6969172,29.522107C3.6969174,29.682307,4.3017105,29.813907,5.0491318,29.813907L21.312653,29.813907C22.059346,29.813907,22.663838,29.682307,22.663838,29.522107L22.663838,19.975152C22.663838,19.815052,22.059346,19.686151,21.312653,19.686151z M1.4384639,10.689999L24.397816,10.689999C25.190506,10.689999,25.835001,11.192702,25.835001,11.811106L25.835001,61.174687C25.835001,61.794492,25.190506,62.296994,24.397816,62.296994L1.4384639,62.296994C0.64354791,62.296994,3.776515E-07,61.794492,0,61.174687L0,11.811106C3.776515E-07,11.192702,0.64354791,10.689999,1.4384639,10.689999z M7.0463995,0L18.789292,0C19.762597,0,20.552,0.78900433,20.552,1.76159L20.552,5.0504084C20.552,6.0229974,19.762597,6.8119996,18.789292,6.8119998L7.0463995,6.8119998C6.0730642,6.8119996,5.2840003,6.0229974,5.2840008,5.0504084L5.2840008,1.76159C5.2840003,0.78900433,6.0730642,0,7.0463995,0z"
Fill="{StaticResource ForegroundBrush}"
Stretch="Uniform" />
</Viewbox>

<TextBlock x:Name="TimeTextBlock"
Grid.RowSpan="2"
Grid.Column="3"
Style="{StaticResource TimeTextBlockStyle}"
Text="14:38" />

<TextBlock x:Name="DayTextBlock"
Grid.Column="5"
Style="{StaticResource DayTextBlockStyle}"
Text="Saturday" />

<TextBlock x:Name="DateTextBlock"
Grid.Row="1"
Grid.Column="5"
Style="{StaticResource DateTextBlockStyle}"
Text="September 29" />
</Grid>
</UserControl>

App.xaml

<Application x:Class="SilverlightMetroClock.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<SolidColorBrush x:Key="ForegroundBrush" Color="White" />
<SolidColorBrush x:Key="BackgroundBrush" Color="#E4000000" />
<Style x:Key="TimeTextBlockStyle" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="/SilverlightMetroClock;component/Fonts/Fonts.zip#Segoe UI Light" />
<Setter Property="FontSize" Value="82" />
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}" />
</Style>
<Style x:Key="DayTextBlockStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="/SilverlightMetroClock;component/Fonts/Fonts.zip#Segoe UI Light" />
<Setter Property="FontSize" Value="32" />
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Margin" Value="0,0,0,-5" />
<Setter Property="VerticalAlignment" Value="Bottom" />
</Style>
<Style x:Key="DateTextBlockStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="/SilverlightMetroClock;component/Fonts/Fonts.zip#Segoe UI Light" />
<Setter Property="FontSize" Value="32" />
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Margin" Value="0,-2,0,0" />
<Setter Property="VerticalAlignment" Value="Top" />
</Style>
</Application.Resources>
</Application>


Online Demo


http://stevenhollidge.com/blog-source-code/silverlightmetroclock/


Download Source Code


https://github.com/stevenh77/SilverlightMetroClock

Metro Bold Colors

 

bold

ShouldThrowException<T> Helper

Many unit test frameworks provide an assert wrapper for an expected exception such as the following in NUnit:

Assert.Throws<T>( code here );

Some frameworks like MS Test don’t and rely on the attributes on the test itself, rather than a specific line of code e.g.

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void GivenConstructorWhenNameIsEmptyThenArgumentExceptionThrown()
{
code
}

If like me you prefer the more targeted nature of the latter so you can use the following code in your unit test project:

public static void ShouldThrowException<T>(Action action) where T : Exception
{
try
{
action();
}
catch (T)
{
return;
}

Assert.Fail("Expected exception did not occur");
}

Here is an example of its usage:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestHelperShouldThrowException.Tests
{
[TestClass]
public class PersonTests
{
[TestMethod]
public void GivenConstructorWhenNameIsFiveCharactersThenPersonIsCreated()
{
Person person = new Person("Tommy");
Assert.IsNotNull(person);
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void GivenConstructorWhenNameIsEmptyThenArgumentExceptionThrown()
{
Person person = new Person("");
}

[TestMethod]
public void GivenConstructoWhenNameIsEmptyThenArgumentExceptionThrownUpdated()
{
Helper.ShouldThrowException<ArgumentException>(() => new Person(""););
}
}

class Person
{
public string Name { get; set; }

public Person(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Invalid name value");

Name = name;
}
}
}

Source: https://github.com/stevenh77/ShouldThrowException

Monday, 18 June 2012

Silverlight ContentMargin Attached Property

Here is a useful Silverlight attached property taken from a Xaml presentation by Miguel Castro.

By setting a property on a Panel it automatically sets the margin property for all its children.

<UserControl x:Class="AttachedProperty.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="clr-namespace:AttachedProperty">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel b:ContentMargin.Margin="10,5,20,15">
<TextBlock HorizontalAlignment="Center" Text="Stack Panel" />
<Button>Button #1</Button>
<Button>Button #2</Button>
<Button>Button #3</Button>
<Button>Button #4</Button>
<Button>Button #5</Button>
<Button>Button #6</Button>
</StackPanel>
</Grid>
</UserControl>


ContentMargin

using System.Windows;
using System.Windows.Controls;

namespace AttachedProperty
{
public static class ContentMargin
{
public static readonly DependencyProperty MarginProperty =
DependencyProperty.RegisterAttached("MarginValue",
typeof (Thickness),
typeof (ContentMargin),
new PropertyMetadata(default(Thickness), OnMarginChanged));

public static Thickness GetMargin(DependencyObject obj)
{
return (Thickness)obj.GetValue(MarginProperty);
}

public static void SetMargin(DependencyObject obj, Thickness value)
{
obj.SetValue(MarginProperty, value);
}

private static void OnMarginChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as FrameworkElement;
if (element == null) return;

element.Loaded += element_Loaded;
}

static void element_Loaded(object sender, RoutedEventArgs e)
{
var panel = sender as Panel;
if (panel == null) return;

foreach (var child in panel.Children)
{
var element = child as FrameworkElement;
if (element == null) continue;
Thickness parentMargin = GetMargin(panel);
element.Margin = new Thickness(parentMargin.Left,
parentMargin.Top,
parentMargin.Right,
parentMargin.Bottom);
}
}
}
}


Source:  https://github.com/stevenh77/AttachedProperty

Saturday, 16 June 2012

Debugging Silverlight Apps

This blog post shows how to investigate a potential memory leak within a Silverlight application.

The Problem

Our simple Silverlight application contains a “Memory Leak” button that removes a “Test View” from a container and replaces it with a new instance of the “Test View”. 

Each time the button is pressed the display counter is incremented (see 8 in the screenshot).

Each “Test View” contains 1 million DummyData objects and we have noticed that each time the button is clicked the memory usage goes up and is never reclaimed.

dump

You can run the example page yourself:  http://stevenhollidge.com/blog-source-code/debuggingsl/

The Code

TestView.cs

using System;
using System.Collections.Generic;

namespace DebuggingSilverlightApp
{
public partial class TestView
{
private const int ListCount = 1000000;
private readonly List<DummyData> largeUseOfMemory = new List<DummyData>(ListCount);

public TestView()
{
InitializeComponent();
var guid = Guid.NewGuid();
var number = new Random().Next(int.MaxValue);

for (int i = 0; i < ListCount; i++)
{
largeUseOfMemory.Add(new DummyData()
{
Id = guid,
Number = number,
Text = "Some irrelevant dummy data consuming memory",
Price = 12345.6789m,
Date = DateTime.Now
});
}
}

public void EventHandler(object sender, EventArgs e)
{
// does nothing
}
}

public class DummyData
{
public Guid Id { get; set; }
public int Number { get; set; }
public string Text { get; set; }
public decimal Price { get; set; }
public DateTime Date { get; set; }
}
}


MainPage.xaml.cs

using System;
using System.Globalization;
using System.Threading;
using System.Windows;

namespace DebuggingSilverlightApp
{
public partial class MainPage
{
public event EventHandler DummyEvent = delegate { };

public int Counter { get; set; }

public MainPage()
{
InitializeComponent();

UpdateDisplayCounter();
}

private void btnHangTime_Click(object sender, RoutedEventArgs e)
{
try
{
throw new Exception("Dummy exception");
}
catch
{
// oh dear, we're swallowing the exception - bad programming!
}
Thread.Sleep(30000);
btnHangTime.Content = "Pushed";
}


private void btnMemoryLeak_Click(object sender, RoutedEventArgs e)
{
// remove the view
ViewContainer.Children.Clear();

// create a new instance of the view
var newView = new TestView();

// the following line is the source of the memory leak, when the
// view is cleared the previous view cannot be garbage collected
DummyEvent += newView.EventHandler;

// add the view to the stackpanel
ViewContainer.Children.Add(newView);

// increment our display counter
this.Counter++;
UpdateDisplayCounter();
}

private void UpdateDisplayCounter()
{
this.CounterTextBlock.Text = Counter.ToString(CultureInfo.InvariantCulture);
}
}
}


Create Dump File


Open Task Manager and select the Internet Explorer process (iexplore.exe), right click and Create Dump File.

Don’t forget to make a copy the location and file name of the dump file.


Install Debugging Tools


First of all we want Windbg, available as part of the Windows Driver Kit (WDK).

http://msdn.microsoft.com/en-us/windows/hardware/gg463009.aspx

The first option contains a Windows 8 Preview download that also contains the correct files for Windows 7, Server 2008 and Vista.  Once this has been selected click the button to download the 1 Gb file.

install-debugging-tools


This installs to:  C:\Program Files (x86)\Windows Kits\8.0\Debuggers\

NOTE:  If you used IE (32 bit) to create the dump file then open the x86 version of windbg.exe.  I use IE (64 bit) so in this example I am using the version found in the x64 folder.


windbg


Next up download the relevant Windows Symbols files: 

http://msdn.microsoft.com/en-us/windows/hardware/gg463028.aspx

When you open Windbg you’ll need to set the “Symbol File Path…”

Once this has been set, you can “Open Crash Dump…”


open-crash-dump


On my machine the Symbol files were installed to C:\Symbols


Windbg commands


Next up we want to run three commands.  Depending on the combination of Silverlight version and architecture (x86 or x64) you’ll want to run the following two commands to setup your Silverlight debug session environment:


Silverlight 4 (x86)


.load C:\Program Files (x86)\Microsoft Silverlight\4.0.60531.0\mscordaccore.dll
.load C:\Program Files (x86)\Microsoft Silverlight\4.0.60531.0\sos.dll

Silverlight 4 (x64)

.load C:\Program Files\Microsoft Silverlight\4.0.60531.0\mscordaccore.dll
.load C:\Program Files\Microsoft Silverlight\4.0.60531.0\sos.dll

Silverlight 5 (x86)


.load C:\Program Files (x86)\Microsoft Silverlight\5.1.10411.0\mscordaccore.dll
.load C:\Program Files (x86)\Microsoft Silverlight\5.1.10411.0\sos.dll

Silverlight 5 (x64)

.load C:\Program Files\Microsoft Silverlight\5.1.10411.0\mscordaccore.dll
.load C:\Program Files\Microsoft Silverlight\5.1.10411.0\sos.dll

Ok, now you are ready to investigate the heap to see what objects are on it:

.dumpheap -stat

results


Here you can see 8 million instances of DummyData taking up 5.76 Gb of space, with 6.8 million "free" objects taking a further 2.58 Gb.  This would point to each view remaining in memory and not being reclaimed by the GC.


Production


You may want to make use of the excellent DebugDiag tool to create your dumps in production based on a certain criteria or rules.

Debug Diag:  http://www.microsoft.com/en-us/download/details.aspx?id=26798


Other Extensions


AdPlus:  Installed as part of the WDK, same directory as Windbg.

PssCor2:  http://www.microsoft.com/en-us/download/details.aspx?id=1073

SOSEx:  http://www.stevestechspot.com/SOSEXV40NowAvailable.aspx


Source Code


https://github.com/stevenh77/DebuggingSilverlightApp

Friday, 15 June 2012

Silverlight Explosions

Here’s an explosive behaviour that was first seen a while ago from Microsoft IT.

Here’s the source:

MainPage.xaml

<UserControl x:Class="Explosions.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Explosions="clr-namespace:Explosions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d" Width="400" Height="500">

<Grid x:Name="LayoutRoot" Background="White">
<StackPanel Margin="20">
<Button x:Name="button" Content="Click me" Margin="20" Height="50" FontSize="29.333">
<i:Interaction.Behaviors>
<Explosions:ExplodeBehavior>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click" SourceObject="{Binding ElementName=button}">
<i:InvokeCommandAction CommandName="Ignite"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Explosions:ExplodeBehavior>
</i:Interaction.Behaviors>
</Button>
<TextBlock x:Name="textBlock" TextWrapping="Wrap" Text="Or click me" HorizontalAlignment="Center" Margin="20" FontSize="29.333" >
<i:Interaction.Behaviors>
<Explosions:ExplodeBehavior>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown" SourceObject="{Binding ElementName=textBlock}">
<i:InvokeCommandAction CommandName="Ignite"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Explosions:ExplodeBehavior>
</i:Interaction.Behaviors>
</TextBlock>
<Image x:Name="image" Height="200" Source="/hoff.PNG" Margin="20">
<i:Interaction.Behaviors>
<Explosions:ExplodeBehavior>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown" SourceObject="{Binding ElementName=image}">
<i:InvokeCommandAction CommandName="Ignite"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Explosions:ExplodeBehavior>
</i:Interaction.Behaviors>
</Image>
</StackPanel>
</Grid>
</UserControl>

ExplosionBehavior.cs

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace Explosions
{
public class ExplodeBehavior : Behavior<FrameworkElement>
{

public static readonly DependencyProperty PowerProperty = DependencyProperty.Register("Power", typeof (double),
typeof (ExplodeBehavior),
new PropertyMetadata(50.0d));

public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof (double),
typeof (ExplodeBehavior),
new PropertyMetadata(
20.0d));

public static readonly DependencyProperty DepthProperty = DependencyProperty.Register("Depth", typeof (double),
typeof (ExplodeBehavior),
new PropertyMetadata(
-20.0d));

public static readonly DependencyProperty ShrapnelAmountProperty = DependencyProperty.Register(
"ShrapnelAmount", typeof (double), typeof (ExplodeBehavior), new PropertyMetadata(500.0d));

public static readonly DependencyProperty EpicenterProperty = DependencyProperty.Register("Epicenter",
typeof (Point),
typeof (
ExplodeBehavior),
new PropertyMetadata(
new Point(.5, .5)));

public static readonly DependencyProperty ReverseDurationProperty =
DependencyProperty.Register("ReverseDuration", typeof (TimeSpan), typeof (ExplodeBehavior),
new PropertyMetadata(TimeSpan.FromSeconds(1)));

private readonly List<Shard> shards = new List<Shard>();
private Point? lastMousePoint;
private Popup popup;

public ExplodeBehavior()
{
}

protected override void OnAttached()
{
base.OnAttached();

this.AssociatedObject.MouseMove += this.HandleMouseMove;
}

protected override void OnDetaching()
{
base.OnDetaching();

this.AssociatedObject.MouseMove -= this.HandleMouseMove;
}

public Point Epicenter
{
get { return (Point) this.GetValue(ExplodeBehavior.EpicenterProperty); }
set { this.SetValue(ExplodeBehavior.EpicenterProperty, value); }
}


public double ShrapnelAmount
{
get { return (double) this.GetValue(ExplodeBehavior.ShrapnelAmountProperty); }
set { this.SetValue(ExplodeBehavior.ShrapnelAmountProperty, value); }
}


public double Depth
{
get { return (double) this.GetValue(ExplodeBehavior.DepthProperty); }
set { this.SetValue(ExplodeBehavior.DepthProperty, value); }
}


public double Radius
{
get { return (double) this.GetValue(ExplodeBehavior.RadiusProperty); }
set { this.SetValue(ExplodeBehavior.RadiusProperty, value); }
}


public double Power
{
get { return (double) this.GetValue(ExplodeBehavior.PowerProperty); }
set { this.SetValue(ExplodeBehavior.PowerProperty, value); }
}

public TimeSpan ReverseDuration
{
get { return (TimeSpan) this.GetValue(ExplodeBehavior.ReverseDurationProperty); }
set { this.SetValue(ExplodeBehavior.ReverseDurationProperty, value); }
}

[DefaultTrigger(typeof (ButtonBase), typeof (System.Windows.Interactivity.EventTrigger), "Click"),
DefaultTrigger(typeof (UIElement), typeof (System.Windows.Interactivity.EventTrigger), "MouseLeftButtonDown")]
public ICommand Ignite
{
get
{
return new DelegateCommand(delegate(object args)
{
this.StartExplode();
});
}
}

public ICommand Implode
{
get
{
return new DelegateCommand(delegate(object args)
{
this.StartImplode();
});
}
}


private void HandleMouseMove(object sender, MouseEventArgs e)
{
this.lastMousePoint = e.GetPosition(this.AssociatedObject);
}

private void StartExplode()
{
if (this.AssociatedObject == null)
return;

Vector3D position;
if (this.ReadLocalValue(ExplodeBehavior.EpicenterProperty) == DependencyProperty.UnsetValue &&
this.lastMousePoint.HasValue)
position = new Vector3D(this.lastMousePoint.Value.X, this.lastMousePoint.Value.Y, this.Depth);
else
position = new Vector3D(this.AssociatedObject.ActualWidth*this.Epicenter.X,
this.AssociatedObject.ActualHeight*this.Epicenter.Y, this.Depth);

this.StartExplode(position);
}

public void StartExplode(double x, double y, double z)
{
this.StartExplode(new Vector3D(x, y, z));
}


private void StartExplode(Vector3D point)
{

if (this.shards.Count == 0)
this.PrepareShards(point);

this.ApplyForce(point);
}

private void StartImplode()
{
Storyboard sb = new Storyboard();
sb.Duration = new Duration(this.ReverseDuration);
foreach (Shard shard in this.shards)
shard.Reverse(this.ReverseDuration, sb);

sb.Completed += delegate
{
if (this.popup != null)
{
this.shards.Clear();
this.AssociatedObject.Opacity = 1;
this.popup.IsOpen = false;
this.popup = null;
}
;
};

sb.Begin();
}

private void PrepareShards(Vector3D point)
{

FrameworkElement content = this.AssociatedObject;
if (content == null)
return;

double width = content.ActualWidth;
double height = content.ActualHeight;

Grid container = new Grid();
this.popup = new Popup();
popup.Child = container;


Point popupOffset = content.TransformToVisual(Application.Current.RootVisual).Transform(new Point(0, 0));

popup.HorizontalOffset = popupOffset.X;
popup.VerticalOffset = popupOffset.Y;
popup.IsHitTestVisible = false;
container.IsHitTestVisible = false;

container.Width = width;
container.Height = height;

double rows = Math.Sqrt(this.ShrapnelAmount*height/width);
double columns = this.ShrapnelAmount/rows;

int rowCount = (int) Math.Round(rows);
int columnCount = (int) Math.Round(columns);

for (int x = 0; x < columnCount; ++x)
container.ColumnDefinitions.Add(new ColumnDefinition()
{
Width = new GridLength(1, GridUnitType.Star),
});

for (int y = 0; y < rowCount; ++y)
container.RowDefinitions.Add(new RowDefinition()
{
Height = new GridLength(1, GridUnitType.Star),
});

WriteableBitmap bitmap = new WriteableBitmap(content, null);
for (int x = 0; x < columnCount; ++x)
{
for (int y = 0; y < rowCount; ++y)
{
Rectangle element = new Rectangle();

ImageBrush brush = new ImageBrush()
{
ImageSource = bitmap,
};

ScaleTransform scale = new ScaleTransform()
{
ScaleX = columnCount,
ScaleY = rowCount,
};

TranslateTransform translation = new TranslateTransform()
{
X = -x/(double) columnCount,
Y = -y/(double) rowCount,
};

TransformGroup transform = new TransformGroup();
transform.Children.Add(translation);
transform.Children.Add(scale);


brush.RelativeTransform = transform;

element.Fill = brush;

Grid.SetColumn(element, x);
Grid.SetRow(element, y);

container.Children.Add(element);

Shard shard = new Shard(element,
new Point(width*(x + .5)/columnCount,
height*(y + .5)/rowCount));

this.shards.Add(shard);
}
}

content.Opacity = 0;
popup.IsOpen = true;
}

private void ApplyForce(Vector3D point)
{
Storyboard sb = new Storyboard()
{
Duration = new Duration(TimeSpan.FromSeconds(100)),
};

foreach (Shard shard in this.shards)
{
Vector3D position = shard.Position;

Vector3D delta = position - point;

double magnitude = -Math.Pow(delta.Length, 2)/(2*this.Radius*this.Radius);
double power = this.Power*Math.Pow(Math.E, magnitude);


delta.Normalize();
delta = delta*power*10;

shard.TranslationVelocity += delta;

Vector3D offset = point - position;

Vector3D rotation = new Vector3D(-offset.Y, -offset.X, offset.Z);

rotation.Normalize();

rotation = rotation*power*10;
rotation.Z = 0;

shard.RotationalVelocity += rotation;

shard.StartAnims(sb);

}
sb.Begin();
}

private class Shard
{

private readonly PlaneProjection projection = new PlaneProjection();
private Point initialPosition;

public Shard(FrameworkElement target, Point initialPosition)
{
this.Target = target;
this.initialPosition = initialPosition;
target.Projection = this.projection;
}

public FrameworkElement Target { get; private set; }

public Vector3D RotationalVelocity { get; set; }
public Vector3D TranslationVelocity { get; set; }

public void Update()
{
this.projection.RotationX += this.RotationalVelocity.X;
this.projection.RotationY += this.RotationalVelocity.Y;
this.projection.RotationZ += this.RotationalVelocity.Z;

this.projection.GlobalOffsetX += this.TranslationVelocity.X;
this.projection.GlobalOffsetY += this.TranslationVelocity.Y;
this.projection.GlobalOffsetZ += this.TranslationVelocity.Z;
}

public Vector3D Position
{
get
{
return new Vector3D(this.initialPosition.X + this.projection.GlobalOffsetX,
this.initialPosition.Y + this.projection.GlobalOffsetY,
this.projection.GlobalOffsetZ);
}
}

public void StartAnims(Storyboard sb)
{
TimeSpan duration = TimeSpan.FromSeconds(100);
sb.Duration = new Duration(duration);

DoubleAnimation tx = new DoubleAnimation()
{
To = this.TranslationVelocity.X*duration.TotalSeconds,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(tx, new PropertyPath(PlaneProjection.GlobalOffsetXProperty));
Storyboard.SetTarget(tx, this.projection);
sb.Children.Add(tx);


DoubleAnimation ty = new DoubleAnimation()
{
To = this.TranslationVelocity.Y*duration.TotalSeconds,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(ty, new PropertyPath(PlaneProjection.GlobalOffsetYProperty));
Storyboard.SetTarget(ty, this.projection);
sb.Children.Add(ty);

DoubleAnimation tz = new DoubleAnimation()
{
To = this.TranslationVelocity.Z*duration.TotalSeconds,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(tz, new PropertyPath(PlaneProjection.GlobalOffsetZProperty));
Storyboard.SetTarget(tz, this.projection);
sb.Children.Add(tz);

DoubleAnimation rx = new DoubleAnimation()
{
To = this.RotationalVelocity.X*duration.TotalSeconds,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(rx, new PropertyPath(PlaneProjection.RotationXProperty));
Storyboard.SetTarget(rx, this.projection);
sb.Children.Add(rx);

DoubleAnimation ry = new DoubleAnimation()
{
To = this.RotationalVelocity.Y*duration.TotalSeconds,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(ry, new PropertyPath(PlaneProjection.RotationYProperty));
Storyboard.SetTarget(ry, this.projection);
sb.Children.Add(ry);

DoubleAnimation rz = new DoubleAnimation()
{
To = this.RotationalVelocity.Z*duration.TotalSeconds,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(rz, new PropertyPath(PlaneProjection.RotationZProperty));
Storyboard.SetTarget(rz, this.projection);
sb.Children.Add(rz);

Storyboard.SetTarget(sb, this.Target);
}

public void Reverse(TimeSpan duration, Storyboard sb)
{
sb.Duration = new Duration(duration);

DoubleAnimation tx = new DoubleAnimation()
{
To = 0,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(tx, new PropertyPath(PlaneProjection.GlobalOffsetXProperty));
Storyboard.SetTarget(tx, this.projection);
sb.Children.Add(tx);


DoubleAnimation ty = new DoubleAnimation()
{
To = 0,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(ty, new PropertyPath(PlaneProjection.GlobalOffsetYProperty));
Storyboard.SetTarget(ty, this.projection);
sb.Children.Add(ty);

DoubleAnimation tz = new DoubleAnimation()
{
To = 0,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(tz, new PropertyPath(PlaneProjection.GlobalOffsetZProperty));
Storyboard.SetTarget(tz, this.projection);
sb.Children.Add(tz);

DoubleAnimation rx = new DoubleAnimation()
{
To = 0,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(rx, new PropertyPath(PlaneProjection.RotationXProperty));
Storyboard.SetTarget(rx, this.projection);
sb.Children.Add(rx);

DoubleAnimation ry = new DoubleAnimation()
{
To = 0,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(ry, new PropertyPath(PlaneProjection.RotationYProperty));
Storyboard.SetTarget(ry, this.projection);
sb.Children.Add(ry);

DoubleAnimation rz = new DoubleAnimation()
{
To = 0,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(rz, new PropertyPath(PlaneProjection.RotationZProperty));
Storyboard.SetTarget(rz, this.projection);
sb.Children.Add(rz);

Storyboard.SetTarget(sb, this.Target);
}
}

private class DelegateCommand : ICommand
{

public delegate void Handler(object args);

private Handler handler;

public DelegateCommand(Handler handler)
{
this.handler = handler;
}

public bool CanExecute(object parameter)
{
return true;
}

public event EventHandler CanExecuteChanged;

public void Execute(object parameter)
{
this.handler(parameter);
}

protected virtual void OnCanExecuteChanged()
{
if (this.CanExecuteChanged != null)
this.CanExecuteChanged(this, EventArgs.Empty);
}

}

private struct Vector3D
{

public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }

public Vector3D(double x, double y, double z)
: this()
{
this.X = x;
this.Y = y;
this.Z = z;
}

public static Vector3D operator -(Vector3D a, Vector3D b)
{
return new Vector3D(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
}

public static Vector3D operator +(Vector3D a, Vector3D b)
{
return new Vector3D(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
}

public static Vector3D operator *(Vector3D vector, double scalar)
{
return new Vector3D(vector.X*scalar, vector.Y*scalar, vector.Z*scalar);
}

public static Vector3D operator /(Vector3D vector, double scalar)
{
return (Vector3D) (vector*(1.0/scalar));
}

public double Length
{
get { return Math.Sqrt(this.X*this.X + this.Y*this.Y + this.Z*this.Z); }
}

public void Normalize()
{
double x = Math.Abs(this.X);
double y = Math.Abs(this.Y);
double z = Math.Abs(this.Z);
if (z > x)
x = z;
if (y > x)
x = y;

this.X /= x;
this.Y /= x;
this.Z /= x;
double length = Math.Sqrt(this.X*this.X + this.Y*this.Y + this.Z*this.Z);
this = (Vector3D) (this/length);
}

public Vector3D Cross(Vector3D other)
{
Vector3D result = new Vector3D();

result.X = this.Y*other.Z - this.Z*other.Y;
result.Y = this.Z*other.X - this.X*other.Z;
result.Z = this.X*other.Y - this.Y*other.X;

return result;
}

public override string ToString()
{
return this.X + "," + this.Y + "," + this.Z;
}
}
}
}

Live demo:  http://stevenhollidge.com/blog-source-code/explosions


Source:  https://github.com/stevenh77/Explosions

Saturday, 9 June 2012

Silverlight StickyNotes for C#

This code is a port of the VB.NET version produced by Billy Hollis, whose Sticky Notes feature in the excellent WPF application Staff Lynx.

The aim is promote, share and cross pollinate ideas and approaches for data entry within forward thinking apps.

Source:  https://github.com/stevenh77/StickyNotesCSharp

Wednesday, 6 June 2012

Silverlight Metro Style Menu

Quick example for creating your own Windows 8 Metro style Menu in Silverlight.

This solution makes use of metro styled: tiles, scroll viewer, app bar (move the mouse to the bottom of the iframe) and a splash screen (refresh the page to see the splash screen).

Online demo:  http://stevenhollidge.com/blog-source-code/metromenu/

Source:  https://github.com/stevenh77/MetroMenu/tree/41e988fa2d588777c324f5591fbe060c8c32f2f8

Second Version

Online demo:  http://stevenhollidge.com/blog-source-code/metromenu2/

Source:  https://github.com/stevenh77/MetroMenu

Silverlight Metro App Bar

Quick example for creating your own Windows 8 Metro style App bar in Silverlight 5.

Source:  https://github.com/stevenh77/MetroAppBar

Tuesday, 5 June 2012

Silverlight Metro Style Splash

With the push toward making apps feel fast and fluid here is a simple animated splash screen.

Online demo:  http://stevenhollidge.com/blog-source-code/metrosplash/

Here’s an animated gif of the splash screen (click the link above for the real thing!):

splash

<UserControl x:Class="MetroSplash.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="userControl"
mc:Ignorable="d">
<UserControl.Projection>
<PlaneProjection />
</UserControl.Projection>

<Grid x:Name="LayoutRoot" Background="White">
<Grid x:Name="BackgroundContainer"
Background="Black"
RenderTransformOrigin="0.5,0.5">
<Grid.Triggers>
<EventTrigger RoutedEvent="UserControl.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="userControl" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)">
<EasingDoubleKeyFrame KeyTime="0" Value="50" />
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="userControl" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.LocalOffsetX)">
<EasingDoubleKeyFrame KeyTime="0" Value="-400" />
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="userControl" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.LocalOffsetZ)">
<EasingDoubleKeyFrame KeyTime="0" Value="-500" />
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="BackgroundContainer" Storyboard.TargetProperty="(UIElement.Opacity)">
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimation d:IsOptimized="True"
Duration="0:0:1.3"
Storyboard.TargetName="image"
Storyboard.TargetProperty="(UIElement.Opacity)"
To="1">
<DoubleAnimation.EasingFunction>
<CircleEase EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<Grid.Projection>
<PlaneProjection />
</Grid.Projection>
<Grid.RenderTransform>
<CompositeTransform />
</Grid.RenderTransform>
<Image x:Name="image"
Height="95"
Margin="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="Images/silverbladetechnology.png"
Stretch="None" />
</Grid>
</Grid>
</UserControl>


Source:  https://github.com/stevenh77/MetroSplash

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>