Blograby

Lottery Numbers Picker (Sharing Animations)

Lottery Numbers Picker helps you choose potentially winning numbers to use when you buy a lottery ticket. First, you can tell it how many numbers it needs to select, what the range of the numbers should be, and whether duplicate numbers are allowed. Then, Lottery Numbers Picker selects the numbers with a fun animation that mimics a machine filled with percolating lottery balls (the kind you see on TV).

The Main Page

Lottery Numbers Picker has a main page, a settings page, and the standard about page.

The User Interface

Listing 16.1 contains the XAML for the main page.

LISTING 16.1 MainPage.xaml—The Main User Interface for Lottery Numbers Picker

[code]

<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.MainPage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”
xmlns:local=”clr-namespace:WindowsPhoneApp”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”Portrait” shell:SystemTray.IsVisible=”True”>
<!– The application bar –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar>
<shell:ApplicationBarIconButton Text=”pick”
IconUri=”/Shared/Images/appbar.play.png”
Click=”PickButton_Click”/>
<shell:ApplicationBarIconButton Text=”settings”
IconUri=”/Shared/Images/appbar.settings.png”
Click=”SettingsButton_Click”/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text=”about” Click=”AboutMenuItem_Click”/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<!– Add one storyboard to the page’s resource dictionary –>
<phone:PhoneApplicationPage.Resources>
<!– This storyboard is applied to each chosen ball, one at a time –>
<Storyboard x:Name=”ChosenBallStoryboard”
Completed=”ChosenBallStoryboard_Completed”>
<!– Makes the chosen ball “blow” upward from the bottom –>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=”(Canvas.Top)”>
<LinearDoubleKeyFrame KeyTime=”0:0:0” Value=”728”/>
<EasingDoubleKeyFrame KeyTime=”0:0:1.5” Value=”100”>
<EasingDoubleKeyFrame.EasingFunction>
<ElasticEase Oscillations=”1” Springiness=”6” EasingMode=”EaseOut”/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
<!– Makes the chosen ball slide to the left, once at the top –>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=”(Canvas.Left)”>
<LinearDoubleKeyFrame KeyTime=”0:0:0” Value=”424”/>
<LinearDoubleKeyFrame KeyTime=”0:0:1.2” Value=”424”/>
<EasingDoubleKeyFrame x:Name=”FinalLeftKeyFrame” KeyTime=”0:0:2”>
<EasingDoubleKeyFrame.EasingFunction>
<BounceEase EasingMode=”EaseOut” Bounces=”1” Bounciness=”8”/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
<!– Spins the chosen ball while it moves up and left, and end upright –>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=
“(local:LotteryBall.RenderTransform).(RotateTransform.Angle)”>
<LinearDoubleKeyFrame KeyTime=”0:0:0” Value=”0”/>
<EasingDoubleKeyFrame KeyTime=”0:0:1.2” Value=”360”>
<EasingDoubleKeyFrame.EasingFunction>
<BounceEase Bounces=”10”/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime=”0:0:2” Value=”0”>
<EasingDoubleKeyFrame.EasingFunction>
<BounceEase EasingMode=”EaseOut” Bounces=”1” Bounciness=”8”/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</phone:PhoneApplicationPage.Resources>
<!– Prevent off-screen visuals from appearing during a page transition –>
<phone:PhoneApplicationPage.Clip>
<RectangleGeometry Rect=”0,0,480,728”/>
</phone:PhoneApplicationPage.Clip>
<Canvas>
<!– Mini-header –>
<TextBlock Text=”LOTTERY NUMBERS PICKER” Margin=”24,16,0,12”
Style=”{StaticResource PhoneTextTitle0Style}”/>
<!– Chosen balls get dynamically added to this canvas –>
<Canvas x:Name=”ChosenBallsCanvas”/>
<!– A canvas filled with percolating plain balls –>
<Canvas>
<local:LotteryBall Percolating=”True” Canvas.Top=”630”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”40” Canvas.Top=”635”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”80” Canvas.Top=”630”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”120” Canvas.Top=”635”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”160” Canvas.Top=”630”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”200” Canvas.Top=”635”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”240” Canvas.Top=”630”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”280” Canvas.Top=”635”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”320” Canvas.Top=”630”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”360” Canvas.Top=”635”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”5” Canvas.Top=”650”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”45” Canvas.Top=”655”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”85” Canvas.Top=”650”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”125” Canvas.Top=”655”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”165” Canvas.Top=”650”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”205” Canvas.Top=”655”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”245” Canvas.Top=”650”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”285” Canvas.Top=”655”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”325” Canvas.Top=”650”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”365” Canvas.Top=”655”/>
<local:LotteryBall Percolating=”True” Canvas.Top=”670”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”40” Canvas.Top=”675”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”80” Canvas.Top=”670”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”120” Canvas.Top=”675”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”160” Canvas.Top=”670”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”200” Canvas.Top=”675”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”240” Canvas.Top=”670”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”280” Canvas.Top=”675”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”320” Canvas.Top=”670”/>
<local:LotteryBall Percolating=”True” Canvas.Left=”360” Canvas.Top=”675”/>
</Canvas>
<!– The container of balls –>
<Rectangle Fill=”{StaticResource PhoneAccentBrush}” Canvas.Top=”153”
Width=”421” Height=”600” Opacity=”.5”/>
</Canvas>
</phone:PhoneApplicationPage>

[/code]

Notes:

The Code-Behind

Listing 16.2 contains the code-behind for the main page.

LISTING 16.2 MainPage.xaml.cs—The Code-Behind for Lottery Numbers Picker’s Main Page

[code]

using System;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
Random random = new Random();
List<int> chosenNumbers = new List<int>();
LotteryBall mostRecentBall;
IApplicationBarIconButton pickButton;
public MainPage()
{
InitializeComponent();
this.pickButton = this.ApplicationBar.Buttons[0]
as IApplicationBarIconButton;
}
void ShowNewBall()
{
// Create a new ball with the correct chosen number.
// The RotateTransform is there so the spinning animation works.
this.mostRecentBall = new LotteryBall {
RenderTransform = new RotateTransform(),
Number = this.chosenNumbers[this.ChosenBallsCanvas.Children.Count]
};
// Assign the storyboard to this new ball
Storyboard.SetTarget(this.ChosenBallStoryboard, this.mostRecentBall);
// Adjust the final horizontal position of the ball based on which one it is
this.FinalLeftKeyFrame.Value = this.mostRecentBall.Width *
this.ChosenBallsCanvas.Children.Count;
// Add the new ball to the canvas
this.ChosenBallsCanvas.Children.Add(this.mostRecentBall);
// Start animating
this.ChosenBallStoryboard.Begin();
}
void ChosenBallStoryboard_Completed(object sender, EventArgs e)
{
// The storyboard must be stopped before its
// target is changed again inside ShowNewBall
this.ChosenBallStoryboard.Stop();
// Manually position the ball in the same spot where the animation left it
Canvas.SetTop(this.mostRecentBall, 100);
Canvas.SetLeft(this.mostRecentBall,
this.mostRecentBall.Width * (this.ChosenBallsCanvas.Children.Count – 1));
// Keep going until enough balls have been chosen
if (this.ChosenBallsCanvas.Children.Count < Settings.NumBalls.Value)
ShowNewBall();
else
this.pickButton.IsEnabled = true;
}
// Application bar handlers
void PickButton_Click(object sender, EventArgs e)
{
this.pickButton.IsEnabled = false;
this.chosenNumbers.Clear();
this.ChosenBallsCanvas.Children.Clear();
// Pick all the numbers
for (int i = 0; i < Settings.NumBalls.Value; i++)
{
// If no duplicate numbers are allowed, keep
// picking until the number is unique
int num;
do
{
num = this.random.Next(Settings.MinNumber.Value,
Settings.MaxNumber.Value + 1);
}
while (!Settings.AllowDuplicates.Value &&
this.chosenNumbers.Contains(num));
this.chosenNumbers.Add(num);
}
// Sort the chosen numbers in increasing numeric order
this.chosenNumbers.Sort();
// Reveal the first ball
ShowNewBall();
}
void SettingsButton_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/SettingsPage.xaml”,
UriKind.Relative));
}
void AboutMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(
“/Shared/About/AboutPage.xaml?appName=Lottery Numbers Picker”,
UriKind.Relative));
}
}
}

[/code]

Notes:

The LotteryBall User Control

The LotteryBall user control used by the main page represents each circle with an optional number centered on it. The XAML for this user control is shown in Listing 16.3. The most interesting part of this XAML is that the control uses a TransformGroup for its translation and rotation rather than a CompositeTransform. This is because a CompositeTransform performs rotation before translation, but the functionality of this control requires that the rotation happens after the translation. Therefore, TransformGroup is used with its TranslateTransform child placed before the RotateTransform child.

LISTING 16.3 LotteryBall.xaml—The User Interface for the LotteryBall User Control

[code]

<UserControl x:Class=”WindowsPhoneApp.LotteryBall”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
FontFamily=”Segoe WP Black” FontSize=”{StaticResource PhoneFontSizeLarge}”
Width=”53” Height=”53” RenderTransformOrigin=”.5,.5”>
<UserControl.RenderTransform>
<TransformGroup>
<TranslateTransform x:Name=”TranslateTransform”/>
<RotateTransform x:Name=”RotateTransform”/>
</TransformGroup>
</UserControl.RenderTransform>
<Grid>
<Ellipse Fill=”{StaticResource PhoneForegroundBrush}”/>
<TextBlock x:Name=”NumberTextBlock” Margin=”1,0,0,3”
Foreground=”{StaticResource PhoneBackgroundBrush}”
HorizontalAlignment=”Center” VerticalAlignment=”Center”/>
</Grid>
</UserControl>

[/code]

Listing 16.4 contains the code-behind for this user control.

LISTING 16.4 LotteryBall.xaml.cs—The Code-Behind for the LotteryBall User Control

[code]

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace WindowsPhoneApp
{
public partial class LotteryBall : UserControl
{
int number;
bool percolating;
Storyboard percolatingStoryboard;
DoubleAnimation percolatingAnimation;
Random random = new Random();
public LotteryBall()
{
InitializeComponent();
}
public int Number
{
get { return this.number; }
set
{
this.NumberTextBlock.Text = value.ToString();
this.number = value;
}
}
public bool Percolating
{
get { return this.percolating; }
set
{
this.percolating = value;
if (this.percolating)
{
// Create a new single-animation storyboard
this.percolatingStoryboard = new Storyboard();
this.percolatingAnimation = new DoubleAnimation { AutoReverse = true };
this.percolatingAnimation.EasingFunction = new QuadraticEase {
EasingMode = EasingMode.EaseInOut };
this.percolatingStoryboard.Children.Add(this.percolatingAnimation);
// Assign the storyboard to this instance’s TranslateTransform and
// animate its Y property to create a “bounce”
Storyboard.SetTarget(this.percolatingStoryboard,
this.TranslateTransform);
Storyboard.SetTargetProperty(this.percolatingStoryboard,
new PropertyPath(“Y”));
// When the “bounce” completes, choose new random values and start
// it again. Repeat indefinitely.
this.percolatingStoryboard.Completed += delegate(object s, EventArgs e)
{
Randomize();
this.percolatingStoryboard.Begin();
};
// Choose random values related to the animation
// and start it for the first time
Randomize();
percolatingStoryboard.Begin();
}
}
}
void Randomize()
{
// Vary the distance and duration of the bounce
this.percolatingAnimation.To = this.random.Next(20, 60) * -1;
this.percolatingAnimation.Duration = TimeSpan.FromMilliseconds(
this.random.Next(50, 200));
// Very the angle of the bounce
this.RotateTransform.Angle = this.random.Next(0, 90) * -1;
}
}
}

[/code]

Notes:

The Settings Page

The Lottery Numbers Picker app uses the following settings defined in Settings.cs:

[code]

public static class Settings
{
public static readonly Setting<int> NumBalls =
new Setting<int>(“NumBalls”, 5);
public static readonly Setting<int> MinNumber =
new Setting<int>(“MinNumber”, 1);
public static readonly Setting<int> MaxNumber =
new Setting<int>(“MaxNumber”, 56);
public static readonly Setting<bool> AllowDuplicates =
new Setting<bool>(“AllowDuplicates”, false);
}

[/code]

The settings page, shown in Figure 16.1, enables the user to adjust all these settings.

FIGURE 16.1 The settings page contains a check box and three sliders.

The User Interface

Listing 16.5 contains the XAML for the settings page.

LISTING 16.5 SettingsPage.xaml—The User Interface for Lottery Numbers Picker’s Settings Page

[code]

<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.SettingsPage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”
xmlns:local=”clr-namespace:WindowsPhoneApp”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”PortraitOrLandscape” shell:SystemTray.IsVisible=”True”>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard settings header –>
<StackPanel Grid.Row=”0” Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock Text=”SETTINGS” Style=”{StaticResource PhoneTextTitle0Style}”/>
<TextBlock Text=”lottery numbers picker”
Style=”{StaticResource PhoneTextTitle1Style}”/>
</StackPanel>
<ScrollViewer Grid.Row=”1”>
<Grid Margin=”{StaticResource PhoneMargin}”>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
</Grid.RowDefinitions>
<!– Allow duplicate numbers –>
<CheckBox x:Name=”AllowDuplicatesCheckBox” HorizontalAlignment=”Left”
Content=”Allow duplicate numbers” local:Tilt.IsEnabled=”True”/>
<!– Number of balls –>
<TextBlock Grid.Row=”1” Text=”Number of balls” Margin=”12,16,0,8”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<TextBlock x:Name=”NumBallsTextBlock” Grid.Row=”1” Margin=”0,16,23,0”
HorizontalAlignment=”Right”
Text=”{Binding Value, ElementName=NumBallsSlider}”/>
<Slider x:Name=”NumBallsSlider” Grid.Row=”2” Minimum=”1” Maximum=”8”
Tag=”{Binding ElementName=NumBallsTextBlock}”
ValueChanged=”Slider_ValueChanged” />
<!– Smallest possible number –>
<TextBlock Grid.Row=”3” Text=”Smallest possible number” Margin=”12,7,0,8”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<TextBlock x:Name=”MinNumberTextBlock” Grid.Row=”3” Margin=”0,7,23,0”
HorizontalAlignment=”Right”
Text=”{Binding Value, ElementName=MinNumberSlider}”/>
<Slider x:Name=”MinNumberSlider” Grid.Row=”4” Minimum=”0” Maximum=”98”
Tag=”{Binding ElementName=MinNumberTextBlock}”
ValueChanged=”Slider_ValueChanged” />
<!– Largest possible number –>
<TextBlock Grid.Row=”5” Text=”Largest possible number” Margin=”12,7,0,8”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<TextBlock x:Name=”MaxNumberTextBlock” Grid.Row=”5” Margin=”0,7,23,0”
HorizontalAlignment=”Right”
Text=”{Binding Value, ElementName=MaxNumberSlider}”/>
<Slider x:Name=”MaxNumberSlider” Grid.Row=”6” Minimum=”1” Maximum=”99”
Tag=”{Binding ElementName=MaxNumberTextBlock}”
ValueChanged=”Slider_ValueChanged” />
</Grid>
</ScrollViewer>
</Grid>
</phone:PhoneApplicationPage>

[/code]

Notes:

The Code-Behind

Listing 16.6 contains the code-behind for the settings page.

LISTING 16.6 SettingsPage.xaml.cs—The Code-Behind for Lottery Numbers Picker’s Settings Page

[code]

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class SettingsPage : PhoneApplicationPage
{
bool initialized = false;
public SettingsPage()
{
InitializeComponent();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Save the chosen settings
Settings.AllowDuplicates.Value =
this.AllowDuplicatesCheckBox.IsChecked.Value;
Settings.NumBalls.Value = (int)this.NumBallsSlider.Value;
Settings.MinNumber.Value = (int)this.MinNumberSlider.Value;
Settings.MaxNumber.Value = (int)this.MaxNumberSlider.Value;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Respect the saved settings
this.AllowDuplicatesCheckBox.IsChecked = Settings.AllowDuplicates.Value;
this.NumBallsSlider.Value = Settings.NumBalls.Value;
this.MinNumberSlider.Value = Settings.MinNumber.Value;
this.MaxNumberSlider.Value = Settings.MaxNumber.Value;
this.initialized = true;
}
void Slider_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e)
{
Slider slider = sender as Slider;
// Round the value so it’s a whole number even when the slider is dragged
slider.Value = Math.Round(slider.Value);
// Don’t do anything until all initial values have been set
if (this.initialized)
{
// Don’t allow min to be higher than max
if (this.MinNumberSlider.Value > this.MaxNumberSlider.Value)
this.MaxNumberSlider.Value = this.MinNumberSlider.Value;
// If the range is too small, auto-allow duplicates
if (this.MinNumberSlider.Value >=
this.MaxNumberSlider.Value – this.NumBallsSlider.Value)
{
this.AllowDuplicatesCheckBox.IsChecked = true;
this.AllowDuplicatesCheckBox.IsEnabled = false;
}
else
{
this.AllowDuplicatesCheckBox.IsEnabled = true;
}
}
}
bool IsMatchingOrientation(PageOrientation orientation)
{
return ((this.Orientation & orientation) == orientation);
}
}
}

[/code]

Slider_ValueChanged forces each slider’s value to an integral value, because it could otherwise become fractional when the user drags the slider. (When this Math.Round assignment actually changes the value, it causes Slider_ValueChanged to be called again. When the value is already a whole number, this does not happen, which is important for preventing an infinite loop.) This method also guards against conditions that would cause the app to crash or hang, enforcing a valid relationship between the minimum number and the maximum number.

The Finished Product

Exit mobile version