Blograby

Vibration Composer

Vibration Composer is probably the strangest app in this part of the book. A cross between a musical instrument and a handheld massager, this app provides an interesting way to create your own custom vibrating patterns.

Users can do some fun things with this app:

This app requires the running-while-the-phone-is-locked capability (automatically granted during the marketplace certification process).Therefore, this app’s marketplace listing will contain the awkward phrase,“This app makes use of your phone’s RunsUnderLock.” (The exact phrase depends on whether you look in Zune or the phone’s Marketplace app, and may change in the future.)

The Main Page

Vibration Composer’s main page represents time in square chunks. Each chunk lasts 1/10th of a second. You can tap any square to toggle it between an activated state and a deactivated state. When you press the app’s start button, it highlights each square in sequence and makes the phone vibrate when an activated square is highlighted. Once it reaches the last activated square, it repeats the sequence from the beginning. Therefore, although the page contains 90 squares (enabling the unique pattern to be up to 9 seconds long), the user is in control over the length of the segment to be repeated. Figure 8.1 demonstrates three simple vibration patterns.

FIGURE 8.1 The pattern to be repeated is automatically trimmed after the last activated square.

The User Interface

Listing 8.1 contains the XAML for the main page.

LISTING 8.1 MainPage.xaml—The User Interface for Vibration Composer’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”
xmlns:toolkit=”clr-namespace:Microsoft.Phone.Controls;
➥assembly=Microsoft.Phone.Controls.Toolkit”
SupportedOrientations=”PortraitOrLandscape”>
<!– The application bar, with three buttons and five menu items –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar>
<shell:ApplicationBarIconButton Text=”start”
IconUri=”/Shared/Images/appbar.play.png” Click=”StartStopButton_Click”/>
<shell:ApplicationBarIconButton Text=”delete”
IconUri=”/Shared/Images/appbar.delete.png” Click=”DeleteButton_Click”/>
<shell:ApplicationBarIconButton Text=”instructions”
IconUri=”/Shared/Images/appbar.instructions.png”
Click=”InstructionsButton_Click”/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text=”solid”/>
<shell:ApplicationBarMenuItem Text=”alternating fast”/>
<shell:ApplicationBarMenuItem Text=”alternating slow”/>
<shell:ApplicationBarMenuItem Text=”sos”/>
<shell:ApplicationBarMenuItem Text=”phone ring”/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<ScrollViewer>
<Grid>
<!– A wrap panel for containing the 90 buttons –>
<toolkit:WrapPanel x:Name=”WrapPanel”/>
<!– A separate rectangle for tracking the current position –>
<Rectangle x:Name=”CurrentPositionRectangle” Visibility=”Collapsed”
Width=”56” Height=”56” Margin=”12” Opacity=”.8”
HorizontalAlignment=”Left” VerticalAlignment=”Top”
Fill=”{StaticResource PhoneForegroundBrush}”/>
</Grid>
</ScrollViewer>
</phone:PhoneApplicationPage>

[/code]

Notes:

The Code-Behind

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

LISTING 8.2 MainPage.xaml.cs—The Code-Behind for Vibration Composer’s Main Page

[code]

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using Microsoft.Devices;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
// The squares
Button[] buttons = new Button[90];
// Remember the current sequence
Setting<IList<bool>> savedSequence = new Setting<IList<bool>>(“Sequence”,
new List<bool>());
DispatcherTimer timer = new DispatcherTimer();
int sequencePosition;
int sequenceEndPosition = -1;
bool isRunning = false;
IApplicationBarIconButton startStopButton;
public MainPage()
{
InitializeComponent();
this.startStopButton = this.ApplicationBar.Buttons[0]
as IApplicationBarIconButton;
// Initialize the timer
this.timer.Interval = TimeSpan.FromSeconds(.1);
this.timer.Tick += Timer_Tick;
// Attach the same Click handler to all menu items in the application bar
foreach (IApplicationBarMenuItem menuItem in this.ApplicationBar.MenuItems)
menuItem.Click += MenuItem_Click;
// Fill the wrap panel with the 90 buttons
for (int i = 0; i < this.buttons.Length; i++)
{
this.buttons[i] = new Button {
// Each button contains a square, invisible (Fill=null) when off and
// accent-colored when on
Content = new Rectangle { Margin = new Thickness(0, 7, 0, 5),
Width = 30, Height = 30 }
};
this.buttons[i].Click += Button_Click;
this.WrapPanel.Children.Add(this.buttons[i]);
}
TrimSequence();
// Allow the app to run (and vibrate) even when the phone is locked.
// Once disabled, you cannot re-enable the default behavior!
PhoneApplicationService.Current.ApplicationIdleDetectionMode =
IdleDetectionMode.Disabled;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Persist the current sequence
this.savedSequence.Value.Clear();
for (int i = 0; i <= this.sequenceEndPosition; i++)
this.savedSequence.Value.Add(this.buttons[i].Tag != null);
// Prevent this from running while instructions are shown
Stop();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Restore the saved sequence, if any
for (int i = 0; i < this.savedSequence.Value.Count; i++)
{
if (this.savedSequence.Value[i])
TurnOn(this.buttons[i]);
}
TrimSequence();
}
// Click handler for each of the 90 buttons
void Button_Click(object sender, RoutedEventArgs e)
{
// Toggle the state of this button
Button b = sender as Button;
if (b.Tag != null)
TurnOff(b);
else
TurnOn(b);
TrimSequence();
}
void TurnOn(Button b)
{
b.Tag = true; // This button is “on”
(b.Content as Rectangle).Fill =
Application.Current.Resources[“PhoneAccentBrush”] as Brush;
}
void TurnOff(Button b)
{
b.Tag = null; // This button is “off”
(b.Content as Rectangle).Fill = null;
}
void TrimSequence()
{
// Find the end of the sequence (the last “on” button)
// and make the remaining buttons dim (opacity of .2)
this.sequenceEndPosition = -1;
for (int i = this.buttons.Length – 1; i >= 0; i–)
{
this.buttons[i].Opacity = 1;
if (this.sequenceEndPosition == -1)
{
if (this.buttons[i].Tag == null)
this.buttons[i].Opacity = .2;
else
this.sequenceEndPosition = i;
}
}
if (this.isRunning && this.sequenceEndPosition == -1)
{
// Force the playback to stop, because a sequenceEndPosition of -1
// means that the sequence is empty (all buttons are off)
StartStopButton_Click(this, EventArgs.Empty);
}
}
void Timer_Tick(object sender, EventArgs e)
{
// Find the wrap-panel-relative location of the current button
Point buttonLocation = this.buttons[this.sequencePosition]
.TransformToVisual(this.WrapPanel).Transform(new Point(0, 0));
// Move the current position rectangle to overlap the current button
this.CurrentPositionRectangle.Margin = new Thickness(
buttonLocation.X + 12, buttonLocation.Y + 12, 0, 0);
// Either start or stop vibrating, based on the state of the current button
if (this.buttons[this.sequencePosition].Tag != null)
VibrateController.Default.Start(TimeSpan.FromSeconds(.5));
else
VibrateController.Default.Stop();
// Advance the current position, and make it loop indefinitely
this.sequencePosition = (this.sequencePosition + 1)
% (this.sequenceEndPosition + 1);
}
void Stop()
{
this.timer.Stop();
VibrateController.Default.Stop();
this.startStopButton.IconUri = new Uri(“/Shared/Images/appbar.play.png”,
UriKind.Relative);
this.startStopButton.Text = “start”;
this.CurrentPositionRectangle.Visibility = Visibility.Collapsed;
this.isRunning = false;
}
void Clear()
{
for (int i = 0; i < this.buttons.Length; i++)
TurnOff(this.buttons[i]);
}
// Application bar handlers
void StartStopButton_Click(object sender, EventArgs e)
{
if (this.isRunning)
{
// Stop and restore all state
Stop();
return;
}
if (this.sequenceEndPosition == -1)
{
MessageBox.Show(“The vibration pattern is empty! You must turn at least “
+ “one square on.”, “Empty Sequence”, MessageBoxButton.OK);
return;
}
// Start
this.startStopButton.IconUri =
new Uri(“/Shared/Images/appbar.stop.png”, UriKind.Relative);
this.startStopButton.Text = “stop”;
this.CurrentPositionRectangle.Visibility = Visibility.Visible;
this.sequencePosition = 0;
this.isRunning = true;
this.timer.Start();
}
void InstructionsButton_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/InstructionsPage.xaml”,
UriKind.Relative));
}
void DeleteButton_Click(object sender, EventArgs e)
{
if (MessageBox.Show(“Are you sure you want to clear the whole sequence?”,
“Delete Sequence”, MessageBoxButton.OKCancel) == MessageBoxResult.OK)
{
Clear();
TrimSequence();
}
}
void MenuItem_Click(object sender, EventArgs e)
{
Clear();
// Grab the text from the menu item to determine the chosen sequence
switch ((sender as IApplicationBarMenuItem).Text)
{
case “solid”:
TurnOn(this.buttons[0]);
break;
case “alternating fast”:
TurnOn(this.buttons[1]);
break;
case “alternating slow”:
TurnOn(this.buttons[6]);
TurnOn(this.buttons[7]);
break;
case “sos”:
TurnOn(this.buttons[10]);
TurnOn(this.buttons[11]);

break;
case “phone ring”:
TurnOn(this.buttons[17]);
TurnOn(this.buttons[21]);

break;
}
TrimSequence();
}
}
}

[/code]

Notes:

When you make your app run under the lock screen, consider making it optional by providing a setting that toggles the value of ApplicationIdleDetectionMode.Unfortunately, although setting it to Disabled takes effect immediately, setting it to Enabled once it’s in this state does not work.Therefore, you must explain to your users that toggling the run-underlock feature requires restarting your app, at least in one direction.

For a brief period of time, the Windows Phone Marketplace required that any app that sets ApplicationIdleDetectionMode to Disabled must provide a way for users to set it to Enabled. For now, this is no longer a requirement.

How does the phone’s vibration on/off setting impact vibrations triggered by my app?

It has no impact.The phone’s vibration setting (found under “ringtones & sounds” in the Settings app) only controls whether incoming phone calls can vibrate.When you use VibrateController, it always makes the phone vibrate. Furthermore, you have no way to discover the phone’s vibration setting, so you can’t manually respect this setting even if you want to.

The Instructions Page

Listing 8.3 contains the XAML for the simple instructions page shown in Figure 8.4.

FIGURE 8.4 The instructions page used by Vibration Composer.

LISTING 8.3 InstructionsPage.xaml—The User Interface for the Instructions Page

[code]

<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.InstructionsPage”
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”
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 header, with some tweaks –>
<StackPanel Margin=”24,16,0,12”>
<TextBlock Text=”VIBRATION COMPOSER” Margin=”-1,0,0,0”
FontFamily=”{StaticResource PhoneFontFamilySemiBold}”
FontSize=”{StaticResource PhoneFontSizeMedium}”/>
<TextBlock Text=”instructions” Margin=”-3,-10,0,0”
FontFamily=”{StaticResource PhoneFontFamilySemiLight}”
FontSize=”{StaticResource PhoneFontSizeExtraExtraLarge}”/>
</StackPanel>
<ScrollViewer Grid.Row=”1”>
<TextBlock Margin=”24,12” TextWrapping=”Wrap”>
Tap the squares to toggle them on and off. …
<LineBreak/><LineBreak/>
When you’re ready to start the vibration, tap the “play” button. …
<LineBreak/><LineBreak/>
The end of the sequence is always the last “on” square. …
<LineBreak/><LineBreak/>
Tap “…” to access a menu of sample sequences.
</TextBlock>
</ScrollViewer>
</Grid>
</phone:PhoneApplicationPage>

[/code]

Listing 8.3 has something new inside its text block: LineBreak elements! You can use LineBreak to insert a carriage return in the middle of a single text block’s text.This only works when the text is specified as the inner content of the element. It cannot be used when assigning the Text attribute to a string.

The Finished Product

 

 

Exit mobile version