Blograby

Moo Can (Turn Over)

Do you remember those cans that moo when you turn them upside down? The Moo Can app brings this classic children’s toy back to life in digital form! Moo Can makes a moo sound when you turn your phone upside down. Gravity also affects the cow on the screen, which rotates and falls toward whatever edge of the screen is currently on the bottom.

Just like the real cans, you can shake the phone to make a harsh sound that is different from the desired “moo” caused by gently turning the phone upside down. You can also change the cow to a sheep or cat, each of which makes its own unique sounds.

The Main Page

Moo Can has a main page, a calibration page, and an instructions page (not shown in this chapter). Listing 47.1 contains the XAML for the main page, and Listing 47.2 contains its code-behind.

LISTING 47.1 MainPage.xaml—The User Interface for Moo Can’s Main Page

[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”
SupportedOrientations=”Portrait”>
<!– The application bar, with five menu items –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar BackgroundColor=”#9CB366” ForegroundColor=”White”>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text=”cow” Click=”AnimalMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”sheep” Click=”AnimalMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”cat” Click=”AnimalMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”instructions”
Click=”InstructionsMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”calibrate”
Click=”CalibrateMenuItem_Click”/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<Canvas>
<Canvas.Background>
<ImageBrush ImageSource=”Images/background.png”/>
</Canvas.Background>
<!– The cow, sheep, or cat –>
<Image x:Name=”AnimalImage” RenderTransformOrigin=”.5,.5”
Canvas.Left=”32” Canvas.Top=”50” Width=”434” Height=”507”>
<Image.RenderTransform>
<CompositeTransform x:Name=”AnimalTransform”/>
</Image.RenderTransform>
</Image>
</Canvas>
</phone:PhoneApplicationPage>

[/code]

The user interface contains an animal image (whose source is set in code-behind) placed over the background image. It also uses an application bar menu, shown in Figure 47.1, for switching the animal or navigating to either of the other two pages. The application bar is given hard-coded colors, so it blends in with the grass in the background image when the menu is closed.

FIGURE 47.1 The application bar menu on the main page.

LISTING 47.2 MainPage.xaml.cs—The Code-Behind for Moo Can’s Main Page

[code]

using System;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using Microsoft.Phone.Applications.Common; // For AccelerometerHelper
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
Setting<string> chosenAnimal = new Setting<string>(“ChosenAnimal”, “cow”);
bool upsideDown = false;
// The start, middle, end, and length of the
// vertical path that the animal moves along
const double MAX_Y = 170;
const double MIN_Y = -75;
const double MID_Y = 47.5;
const double LENGTH_Y = 245;
// The start, middle and end of the clockwise rotation of the animal
const double MAX_ANGLE_CW = 180;
const double MIN_ANGLE_CW = 0;
const double MID_ANGLE_CW = 90;
// The start, middle and end of the counter-clockwise rotation of the animal
const double MIN_ANGLE_CCW = -180;
const double MAX_ANGLE_CCW = 0;
const double MID_ANGLE_CCW = -90;
// The length of the rotation, regardless of which direction
const double LENGTH_ANGLE = 180;
public MainPage()
{
InitializeComponent();
// Use the accelerometer via Microsoft’s helper
AccelerometerHelper.Instance.ReadingChanged += Accelerometer_ReadingChanged;
SoundEffects.Initialize();
// Allow the app to run (producing sounds) even when the phone is locked.
// Once disabled, you cannot re-enable the default behavior!
PhoneApplicationService.Current.ApplicationIdleDetectionMode =
IdleDetectionMode.Disabled;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Restore the chosen animal
ChangeAnimal();
// Start the accelerometer with Microsoft’s helper
AccelerometerHelper.Instance.Active = true;
// While on this page, don’t allow the screen to auto-lock
PhoneApplicationService.Current.UserIdleDetectionMode =
IdleDetectionMode.Disabled;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Restore the ability for the screen to auto-lock when on other pages
PhoneApplicationService.Current.UserIdleDetectionMode =
IdleDetectionMode.Enabled;
}
void ChangeAnimal()
{
switch (this.chosenAnimal.Value)
{
case “cow”:
this.AnimalImage.Source = new BitmapImage(
new Uri(“Images/cow.png”, UriKind.Relative));
break;
case “sheep”:
this.AnimalImage.Source = new BitmapImage(
new Uri(“Images/sheep.png”, UriKind.Relative));
break;
case “cat”:
this.AnimalImage.Source = new BitmapImage(
new Uri(“Images/cat.png”, UriKind.Relative));
break;
}
}
// Process data coming from the accelerometer
void Accelerometer_ReadingChanged(object sender,
AccelerometerHelperReadingEventArgs e)
{
// Transition to the UI thread
this.Dispatcher.BeginInvoke(delegate()
{
// Move the animal vertically based on the vertical force
this.AnimalTransform.TranslateY =
Clamp(MID_Y – LENGTH_Y * e.AverageAcceleration.Y, MIN_Y, MAX_Y);
// Clear the upside-down flag, only when completely upright
if (this.AnimalTransform.TranslateY == MAX_Y)
this.upsideDown = false;
// Rotate the animal to always be upright
if (e.AverageAcceleration.X <= 0)
this.AnimalTransform.Rotation = Clamp(MID_ANGLE_CW +
LENGTH_ANGLE * e.AverageAcceleration.Y, MIN_ANGLE_CW, MAX_ANGLE_CW);
else
this.AnimalTransform.Rotation = Clamp(MID_ANGLE_CCW –
LENGTH_ANGLE * e.AverageAcceleration.Y, MIN_ANGLE_CCW, MAX_ANGLE_CCW);
// Play the appropriate shake sound when shaken
if (ShakeDetection.JustShook(e.OriginalEventArgs))
{
switch (this.chosenAnimal.Value)
{
case “cow”: SoundEffects.MooShake.Play(); break;
case “sheep”: SoundEffects.BaaShake.Play(); break;
case “cat”: SoundEffects.MeowShake.Play(); break;
}
}
// Play the normal sound when first turned upside-down
if (!this.upsideDown && this.AnimalTransform.TranslateY == MIN_Y)
{
this.upsideDown = true;
switch (this.chosenAnimal.Value)
{
case “cow”: SoundEffects.Moo.Play(); break;
case “sheep”: SoundEffects.Baa.Play(); break;
case “cat”: SoundEffects.Meow.Play(); break;
}
}
});
}
// “Clamp” the incoming value so it’s no lower than min & no larger than max
static double Clamp(double value, double min, double max)
{
return Math.Max(min, Math.Min(max, value));
}
// Application bar handlers
void AnimalMenuItem_Click(object sender, EventArgs e)
{
this.chosenAnimal.Value = (sender as IApplicationBarMenuItem).Text;
ChangeAnimal();
}
void InstructionsMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/InstructionsPage.xaml”,
UriKind.Relative));
}
void CalibrateMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(
“/Shared/Calibrate/CalibratePage.xaml?appName=Moo Can”, UriKind.Relative));
}
}
}

[/code]

FIGURE 47.2 The chosen animal rotates and falls as you turn the phone upside down.

AccelerometerHelperReadingEventArgs exposes four properties that enable you to choose how raw or smooth you want your data to be:

Each property exposes X, Y, Z, and Magnitude properties (but unlike AccelerometerReadingEventArgs, no Timestamp property). Magnitude reports the length of the 3D vector formed by the other three values, which is the square root of X2 + Y2 + Z2. For more details about the algorithms behind these properties, see http://bit.ly/accelerometerhelper.

The Calibration Page

Moo Can enables users to calibrate the accelerometer in case its notion of right side up or upside down are slightly askew from reality. The process of calibration simply involves asking the user when they believe the phone is level, remembering the accelerometer’s reading at that moment, and then using those values as an offset to the accelerometer data from that point onward.

One benefit of using AccelerometerHelper rather than the raw accelerometer APIs is that calibration functionality is built in. It collects the data, stores it as isolated storage application settings (named “AccelerometerCalibrationX” and “AccelerometerCalibrationY”), and automatically offsets the data it returns—even the supposedly “raw” data.

The calibration page, designed to be shared among multiple apps, shows you how to take advantage of it. Listing 47.3 contains the XAML and Listing 47.4 contains the codebehind. It produces the page shown in Figure 47.3.

FIGURE 47.3 The calibration page enables calibrating just the X dimension, just the Y dimension, or both, but only when the phone is held fairly still and level.

LISTING 47.3 CalibratePage.xaml—The User Interface for The Accelerometer Calibration Page

[code]

<phone:PhoneApplicationPage x:Class=”WindowsPhoneApp.CalibratePage”
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:local=”clr-namespace:WindowsPhoneApp”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”PortraitOrLandscape”>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard header –>
<StackPanel Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock x:Name=”ApplicationName”
Style=”{StaticResource PhoneTextTitle0Style}”/>
<TextBlock Text=”calibrate” Style=”{StaticResource PhoneTextTitle1Style}”/>
</StackPanel>
<ScrollViewer Grid.Row=”1”>
<StackPanel>
<TextBlock Margin=”24” TextWrapping=”Wrap” Text=”Tap the button …”/>
<Button x:Name=”CalibrateButton” Content=”calibrate” IsEnabled=”False”
Height=”150” Click=”CalibrateButton_Click”
local:Tilt.IsEnabled=”True”/>
<TextBlock x:Name=”WarningText” Visibility=”Collapsed” Margin=”24,0”
TextWrapping=”Wrap” FontWeight=”Bold”
Text=”Your phone is not still or level enough!”/>
</StackPanel>
</ScrollViewer>
</Grid>
</phone:PhoneApplicationPage>

[/code]

LISTING 47.4 CalibratePage.xaml.cs—The Code-Behind for the Accelerometer Calibration Page

[code]

using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Applications.Common; // For AccelerometerHelper
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class CalibratePage : PhoneApplicationPage
{
bool calibrateX = true, calibrateY = true;
public CalibratePage()
{
InitializeComponent();
// Use the accelerometer via Microsoft’s helper
AccelerometerHelper.Instance.ReadingChanged += Accelerometer_ReadingChanged;
// Ensure it is active
AccelerometerHelper.Instance.Active = true;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Set the application name in the header
if (this.NavigationContext.QueryString.ContainsKey(“appName”))
{
this.ApplicationName.Text =
this.NavigationContext.QueryString[“appName”].ToUpperInvariant();
}
// Check for calibration parameters
if (this.NavigationContext.QueryString.ContainsKey(“calibrateX”))
{
this.calibrateX =
bool.Parse(this.NavigationContext.QueryString[“calibrateX”]);
}
if (this.NavigationContext.QueryString.ContainsKey(“calibrateY”))
{
this.calibrateY =
bool.Parse(this.NavigationContext.QueryString[“calibrateY”]);
}
}
// Process data coming from the accelerometer
void Accelerometer_ReadingChanged(object sender,
AccelerometerHelperReadingEventArgs e)
{
this.Dispatcher.BeginInvoke(delegate()
{
bool canCalibrateX = this.calibrateX &&
AccelerometerHelper.Instance.CanCalibrate(this.calibrateX, false);
bool canCalibrateY = this.calibrateY &&
AccelerometerHelper.Instance.CanCalibrate(false, this.calibrateY);
// Update the enabled state and text of the calibration button
this.CalibrateButton.IsEnabled = canCalibrateX || canCalibrateY;
if (canCalibrateX && canCalibrateY)
this.CalibrateButton.Content = “calibrate (flat)”;
else if (canCalibrateX)
this.CalibrateButton.Content = “calibrate (portrait)”;
else if (canCalibrateY)
this.CalibrateButton.Content = “calibrate (landscape)”;
else
this.CalibrateButton.Content = “calibrate”;
this.WarningText.Visibility = this.CalibrateButton.IsEnabled ?
Visibility.Collapsed : Visibility.Visible;
});
}
void CalibrateButton_Click(object sender, RoutedEventArgs e)
{
if (AccelerometerHelper.Instance.Calibrate(this.calibrateX,
this.calibrateY) ||
AccelerometerHelper.Instance.Calibrate(this.calibrateX, false) ||
AccelerometerHelper.Instance.Calibrate(false, this.calibrateY))
{
// Consider it a success if we were able to
// calibrate in either direction (or both)
if (this.NavigationService.CanGoBack)
this.NavigationService.GoBack();
}
else
{
MessageBox.Show(“Unable to calibrate. Make sure you’re holding your “ +
“phone still, even when tapping the button!”, “Calibration Error”,
MessageBoxButton.OK);
}
}
}
}

[/code]

Exit mobile version