Subservient Cat (Video)

Subservient Cat is a sort of “virtual pet” app. Unlike most cats, the subservient cat can actually obey commands! However, users have to figure out what commands the cat will respond to. It’s a bit of a game, because users must keep guessing to see how many commands they can discover.

This app uses video footage of a black cat (whose real name is Boo) as its primary user interface. Therefore, this is the perfect app for learning about MediaElement, the element for playing video inside apps.

Playing Video with MediaElement

If you want to enable users to play, pause, and otherwise control videos, your best option is to use the Media Player launcher. However, if you want to play video inline on your own page, MediaElement enables you to do so.

MediaElement is a UI element that renders the video specified via its Source property, for example:

[code]

<MediaElement Source=”cat.wmv”/>

[/code]

The source URI can point to a file included in your project or to an online video. By default, MediaElement automatically starts playing the video as soon as the element is rendered (or as soon as enough of the video has been downloaded, in the online case), but you can change this by setting its AutoPlay property to false. MediaElement has Play, Pause, and Stop methods that can be used from code-behind. It also has a Position property that reveals the current playback position (as a time span). If the video supports seeking, you can also set Position to move playback to that point in time. MediaElement can be transformed and clipped just like other Silverlight elements, and it can blended amongst other elements.

On the surface, it appears that MediaElement is straightforward to use. It has tons of caveats, however. Several warnings follow that describe some of the biggest caveats.

An app’s frame can only contain one MediaElement!

Attempting to use more than one MediaElement is not supported and fails in various ways.Note that the limitation is more strict than one per page; only one can be attached to the frame at any time. (It doesn’t matter if they’re stopped, paused, or playing.) Therefore, multiple pages can each have a MediaElement only if they never reside on the navigation stack at the same time. Otherwise, if you need to play multiple videos, you’ll either need to reuse the same MediaElement or remove the unused MediaElement from the element tree.

When including a video file in your app, make sure its Build Action is set to Content, not Resource!

This improves the performance of starting the video. When the video is embedded as a resource, it is first extracted and temporarily saved to isolated storage before it is played! (The same warning applies to audio files played by MediaElement, although this is not normally done.)

When MediaElement starts playing, any background audio (such as music playing from Zune) is paused!

This is the main reason that MediaElement should never be used for playing sound effects. Note that this is true even if your video contains no audio.

MediaElement doesn’t work in the emulator under the light theme!

It’s strange, but true.To test your use of a MediaElement on the emulator, you must ensure that it is running under the dark theme.Don’t worry; this bug doesn’t affect real phones.

MediaElement doesn’t render fully opaque!

If any elements exist underneath a MediaElement, you can see them clearly through the video, even when MediaElement’s Opacity property is set to 1 (as it is by default)! This is an anomaly with the composition between the phone’s media player (which internally renders MediaElement’s video) and the rest of Silverlight.

See “Supported Media Codecs forWindows Phone” (http://goo.gl/6NhuD) for details about the video formats supported by MediaElement and the “Recommended Video Encoding Settings” section of http://goo.gl/ttPkO for more details about what encodings work best. If you use Expression Encoder, you can encode videos with a preconfigured profile optimized for Windows Phone (and Zune HD).

The User Interface

The Subservient Cat app uses a single page—MainPage—in addition to its instructions page (not shown in this chapter). The XAML for MainPage is shown in Listing 33.1. The root grid contains three distinct pieces of user interface, all shown in Figure 33.1:

  • The MediaElement that contains the video
  • A simple “intro screen” that introduces each command done by the cat before the corresponding video clip plays
  • A panel with a text box for guessing new commands
The three main components of the user interface on the main page.
FIGURE 33.1 The three main components of the user interface on the main page.

LISTING 33.1 MainPage.xaml—The User Interface for Subservient Cat’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”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”Landscape” Orientation=”Landscape”>
<!– The application bar–>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar Opacity=”.8”>
<shell:ApplicationBarIconButton Text=”command”
IconUri=”/Shared/Images/appbar.command.png”
Click=”CommandButton_Click”/>
<shell:ApplicationBarIconButton Text=”instructions”
IconUri=”/Shared/Images/appbar.instructions.png”
Click=”InstructionsButton_Click”/>
<shell:ApplicationBarIconButton Text=”discovered”
IconUri=”/Shared/Images/appbar.1.png”
Click=”DiscoveredButton_Click”/>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<Grid>
<!– The video, scaled to fill the page without visible letterboxing –>
<MediaElement x:Name=”MediaElement” Source=”cat.wmv” Stretch=”None”
Volume=”1” MediaOpened=”MediaElement_MediaOpened”
MediaFailed=”MediaElement_MediaFailed”>
<MediaElement.RenderTransform>
<CompositeTransform TranslateY=”-115” TranslateX=”-150”
ScaleX=”1.68” ScaleY=”1.68”/>
</MediaElement.RenderTransform>
</MediaElement>
<!– Something to show on top of the video during seeking –>
<Border x:Name=”IntroScreen” Background=”#6E5962” Visibility=”Collapsed”>
<Grid>
<Image Source=”Images/paw.png” Stretch=”None” HorizontalAlignment=”Left”
VerticalAlignment=”Top” Margin=”100,100,0,0”/>
<Image Source=”Images/paw.png” Stretch=”None” HorizontalAlignment=”Left”
VerticalAlignment=”Top” Margin=”230,50,0,0”/>
<Image Source=”Images/paw.png” Stretch=”None” HorizontalAlignment=”Left”
VerticalAlignment=”Top” Margin=”628,300,0,0”/>
<Image Source=”Images/paw.png” Stretch=”None” HorizontalAlignment=”Left”
VerticalAlignment=”Top” Margin=”528,350,0,0”/>
<TextBlock x:Name=”NextCommandTextBlock” Foreground=”Black” FontSize=”40”
VerticalAlignment=”Center” HorizontalAlignment=”Center”/>
</Grid>
</Border>
<!– The user interface for typing new commands –>
<Grid x:Name=”CommandPanel”
Background=”#A000”
Visibility=”Collapsed” VerticalAlignment=”Bottom”>
<Grid.RowDefinitions>
<RowDefinition/>
<!– A bottom margin, so the auto-scroll while the textbox has focus
doesn’t show extra space below the video –>
<RowDefinition Height=”84”/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=”Auto”/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Source=”/Shared/Images/appbar.command.png” Stretch=”None”
Margin=”70,0,0,0”/>
<Rectangle Grid.Column=”1” Fill=”White” Margin=”12,12,82,12”/>
<TextBox x:Name=”CommandTextBox” Grid.Column=”1” InputScope=”Text”
Margin=”0,0,70,0” TextChanged=”CommandTextBox_TextChanged”
LostFocus=”CommandTextBox_LostFocus”/>
</Grid>
</Grid>
</phone:PhoneApplicationPage>

[/code]

  • The video is landscape-oriented, so this is a landscape-only page. However, because a text box is used for guessing new commands, the code-behind temporarily changes SupportedOrientations to allow any orientation while the text box has focus. This way, phones with portrait hardware keyboards still get a good experience.
  • The application bar has three buttons: one for revealing the command input panel, one for navigating to the instructions page, and one whose icon reveals how many commands have been discovered (updated in code-behind). Tapping this last button also tells you whether there are more commands to be discovered, as the total number is a secret. The application bar menu, dynamically filled from code-behind, contains the list of discovered commands and makes the cat perform each one when tapped. This is shown in Figure 33.2.
The application bar menu provides quick access to the list of already-discovered commands, such as “yawn.”
FIGURE 33.2 The application bar menu provides quick access to the list of already-discovered commands, such as “yawn.”
  • Although the app appears to play several different short videos, it actually uses a single longer video (cat.wmv) for performance reasons. The code-behind is responsible for playing the appropriate segments of the video at the appropriate times.
  • The MediaElement is moved and enlarged with a CompositeTransform because the source cat.wmv file has black bars along the top and bottom that we don’t want to see on the screen.
  • MediaElement’s Volume property (a value from 0 to 1) is set to 1 (the loudest possible volume) because the default value is .85. Although the sound can only be as loud as the user’s volume level, this helps ensure that the tiny bit of audio in this app’s video (a short meow) can be heard.

Be sure to give your MediaElement a name!

If you don’t, it’s possible that the marketplace publishing process won’t detect your use of MediaElement, and therefore will not grant your app the “media library” capability that is necessary for your app to work.

The Code-Behind

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

LISTING 33.2 MainPage.xaml.cs—The Code-Behind for Subservient Cat’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.Threading;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
// Captures the slice in the big video where each command resides
class VideoClip
{
public TimeSpan Start;
public TimeSpan End;
}
DispatcherTimer videoTimer = new DispatcherTimer();
DispatcherTimer delayTimer = new DispatcherTimer();
VideoClip pendingNewClip;
Dictionary<string, VideoClip> possibleCommands =
new Dictionary<string, VideoClip>();
Dictionary<string, string> aliases = new Dictionary<string, string>();
// Start users off with one known command: yawn
Setting<List<string>> discoveredCommands =
new Setting<List<string>>(“DiscoveredCommands”,
new List<string>(new string[] { “yawn” }));
IApplicationBarIconButton discoveredButton;
public MainPage()
{
InitializeComponent();
this.discoveredButton = this.ApplicationBar.Buttons[2]
as IApplicationBarIconButton;
this.videoTimer.Tick += VideoTimer_Tick;
this.delayTimer.Tick += DelayTimer_Tick;
// All the commands and their positions in the video
this.possibleCommands.Add(“yawn”, new VideoClip {
Start = TimeSpan.FromSeconds(98.36),
End = TimeSpan.FromSeconds(101.28) });
this.possibleCommands.Add(“meow”, new VideoClip {
Start = TimeSpan.FromSeconds(79.4),
End = TimeSpan.FromSeconds(81.863) });

// Permitted variations for each command
this.aliases.Add(“yawn”, “yawn”);
this.aliases.Add(“meow”, “meow”);
this.aliases.Add(“speak”, “meow”);
this.aliases.Add(“talk”, “meow”);
this.aliases.Add(“say your name”, “meow”);

}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Fill the application bar menu with all previously-discovered commands
this.ApplicationBar.MenuItems.Clear();
foreach (string command in this.discoveredCommands.Value)
{
ApplicationBarMenuItem item = new ApplicationBarMenuItem(command);
item.Click += ApplicationBarMenuItem_Click;
this.ApplicationBar.MenuItems.Add(item);
}
// Show how many commands have been discovered on the button imag
this.discoveredButton.IconUri = new Uri(“/Shared/Images/appbar.”
+ this.discoveredCommands.Value.Count + “.png”, UriKind.Relative);
}
void MediaElement_MediaOpened(object sender, RoutedEventArgs e)
{
// Play a short intro clip
this.videoTimer.Interval = TimeSpan.FromSeconds(1.48);
this.videoTimer.Start();
}
void MediaElement_MediaFailed(object sender, ExceptionRoutedEventArgs e)
{
MessageBox.Show(“To see the subservient cat, please disconnect your “ +
“phone from Zune.”, “Please Disconnect”, MessageBoxButton.OK);
}
void PlayClip(string title, TimeSpan beginTime, TimeSpan endTime)
{
// Set up the timer to stop playback approximately when endTime is reached
this.videoTimer.Stop();
this.videoTimer.Interval = endTime – beginTime;
// Hide the video and show the intro screen
this.MediaElement.Pause();
this.NextCommandTextBlock.Text = title;
this.IntroScreen.Visibility = Visibility.Visible;
// Give the intro screen a chance to show before doing the following work
this.Dispatcher.BeginInvoke(delegate()
{
// Delay the reappearance of the video until after the seek completes
this.delayTimer.Interval = TimeSpan.FromSeconds(2);
this.delayTimer.Start();
// Seek to the correct spot in the video
this.MediaElement.Position = beginTime;
});
}
void VideoTimer_Tick(object sender, EventArgs e)
{
// We’ve reached the end of the current clip, so pause the video
this.MediaElement.Pause();
// Prevent the timer from continuing to tick
this.videoTimer.Stop();
}
void DelayTimer_Tick(object sender, EventArgs e)
{
// This timer is used for two reasons, either to delay the execution of a
// new command after typing it in, or to delay the beginning of playing a
// video clip after the intro screen is shown.
if (this.IntroScreen.Visibility == Visibility.Collapsed)
{
// This is the execution of a new command
string text = this.CommandTextBox.Text;
this.CommandTextBox.Foreground = new SolidColorBrush(Colors.Black);
this.CommandTextBox.Text = “”;
this.Focus(); // Closes the command input panel
PlayClip(text.ToLowerInvariant(), this.pendingNewClip.Start,
this.pendingNewClip.End);
}
else
{
// We’re ready to actually play the video clip
this.videoTimer.Start();
this.MediaElement.Play();
this.IntroScreen.Visibility = Visibility.Collapsed;
}
// Prevent the timer from continuing to tick
this.delayTimer.Stop();
}
void CommandTextBox_LostFocus(object sender, RoutedEventArgs e)
{
// Restore the page to landscape-only and hide the command input panel
this.SupportedOrientations = SupportedPageOrientation.Landscape;
this.CommandPanel.Visibility = Visibility.Collapsed;
}
void CommandTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
string text = this.CommandTextBox.Text.Trim().ToLowerInvariant();
// Don’t bother checking when the text is shorter than any valid command
if (text.Length < 3)
return;
if (this.aliases.ContainsKey(text))
{
string commandName = this.aliases[text];
// Only acknowledge the command if it’s not already in the list
if (!this.discoveredCommands.Value.Contains(text))
{
// Signal a successful guess
this.CommandTextBox.Foreground =
Application.Current.Resources[“PhoneAccentBrush”] as Brush;
this.pendingNewClip = this.possibleCommands[commandName];
// Append the new command to the application bar menu
ApplicationBarMenuItem item = new ApplicationBarMenuItem(text);
item.Click += ApplicationBarMenuItem_Click;
this.ApplicationBar.MenuItems.Add(item);
// Record the discovered command
this.discoveredCommands.Value.Add(text);
this.discoveredButton.IconUri = new Uri(“/Shared/Images/appbar.” +
this.discoveredCommands.Value.Count + “.png”, UriKind.Relative);
// Wait a second before hiding the text box and showing the intro screen
this.delayTimer.Interval = TimeSpan.FromSeconds(1);
this.delayTimer.Start();
}
}
}
// Application bar handlers
void ApplicationBarMenuItem_Click(object sender, EventArgs e)
{
IApplicationBarMenuItem item = sender as IApplicationBarMenuItem;
// Grab the right clip based on the menu item’s text
VideoClip command = this.possibleCommands[this.aliases[item.Text]];
this.Focus(); // In case the command input panel is currently showing
PlayClip(item.Text, command.Start, command.End);
}
void CommandButton_Click(object sender, EventArgs e)
{
// Temporarily allow a portrait orientation while the text box is in use
this.SupportedOrientations = SupportedPageOrientation.PortraitOrLandscape;
this.CommandPanel.Visibility = Visibility.Visible;
// Enable automatic deployment of the software keyboard
this.CommandTextBox.Focus();
this.CommandTextBox.SelectAll();
}
void InstructionsButton_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/InstructionsPage.xaml”,
UriKind.Relative));
}
void DiscoveredButton_Click(object sender, EventArgs e)
{
// Pause, otherwise the video will keep playing while the message box is
// shown and the timer Tick handler won’t be raised to stop it!
this.MediaElement.Pause();
if (this.discoveredCommands.Value.Count == this.possibleCommands.Count)
{
MessageBox.Show(“Congratulations! You have discovered all the commands!”,
“Discovered Commands”, MessageBoxButton.OK);
}
else if (this.discoveredCommands.Value.Count == 1)
{
MessageBox.Show(“You have discovered only one command, and it was given” +
“ to you! Keep trying! (You can see and use your command by tapping” +
“ ”…”)”, “Discovered Commands”, MessageBoxButton.OK);
}
else
{
MessageBox.Show(“You have discovered “ +
this.discoveredCommands.Value.Count + “ commands. (You can see them” +
“ and use them by tapping ”…”) There are more to discover!”,
“Discovered Commands”, MessageBoxButton.OK);
}
}
}
}

[/code]

  • The constructor populates possibleCommands with the list of commands along with their starting and ending times in the cat.wmv video. Because guessing each command by its exact name can be incredibly hard, an aliases dictionary is used that enables alternate forms of some commands, such as “speak” instead of “meow.”
  • In OnNavigatedTo, the application bar menu is filled with all previously discovered commands. These are stored in discoveredCommands, which is a Setting so it always gets persisted.
  • In order to show the number of discovered commands in the application bar button’s icon, several distinct images are included in this project: appbar.1.png, appbar.2.png, appbar.3.png, and so on. The right one is selected based on the Count of the discoveredCommands collection.
  • The video starts automatically playing when the page is loaded (because AutoPlay was not set to false in Listing 33.1), but we do not want the entire video to play and reveal all the cat’s actions. Instead, only the first second and a half should play. Therefore, in MediaElement’s MediaOpened event handler (raised when the media has loaded and is ready to start playing), videoTimer is used to pause the video after 1.48 seconds have elapsed. The pausing is done in videoTimer’s Tick event handler, VideoTimer_Tick.

You cannot call Play on a MediaElement until its MediaOpened event is raised!

When a MediaElement’s Source is set (either in XAML or code-behind), you cannot instantly begin interacting with the media. Instead, you must wait for MediaOpened to be raised. If the media cannot be loaded for some reason, a MediaFailed event is raised instead. Subservient Cat avoids the need to manually call Play because it uses the auto-play feature, but if it didn’t use auto-play, then it should call Play inside MediaElement_MediaOpened.

Why doesn’t my video play on a physical phone while it is connected to a computer running Zune?

This is due to the same problem discussed in the preceding chapter.The Zune desktop program locks the media library, which prevents MediaElement from loading its media. Remember, if you need to debug a part of your app that depends on video actually playing, you can use the Windows Phone Connect Tool that ships with the Windows Phone Developer Tools to connect to your phone without Zune running. Subservient Cat detects this situation by handling the MediaFailed event. It assumes the failure is due to the Zune connection, which is a pretty safe assumption because the source video is local to the app.

  • The PlayClip method ensures that the video is paused, seeks to the specified beginTime, and reinitializes videoTimer so it pauses the video at endTime. However, because setting MediaElement’s Position has an annoying effect of seeing the video quickly fastforward or rewind to the desired spot (rather than an instant jump), the intro screen is shown to hide the video during the transition. (We don’t want to reveal yet-to-be-discovered parts of the video!) The setting of Position is done inside a BeginInvoke callback so the showing of the intro screen has a chance to take effect. Without this, you still see the unwanted behavior. Two seconds is chosen as the length of the delay, during which the user can read the command text on the intro screen. We don’t have a way to know when seek has actually finished, but two seconds is long enough.

When you set MediaElement’s Position, the change is not instantaneous!

Instead, you see a little bit of the video before or after the new position, as if you’re watching the video in fast-forward or rewind mode.The workaround used by Subservient Cat is to temporarily obscure the video with other elements.

In the current version of Windows Phone, MediaElement does not support markers.The use of markers to designate when individual clips inside cat.wmv begin and end would have been ideal, and would have greatly reduced the amount of code needed in Listing 33.2. However, using a DispatcherTimer to be notified when the relevant clip has ended is a reasonable workaround.There are two things to be aware of, however:

  • The timer doesn’t give you frame-by-frame accuracy.The video used by this app has a little bit of a buffer at the end of each clip, in case videoTimer’s Tick event gets raised slightly late.
  • If you show a message box, the video will keep playing in the background but the timer’s Tick event handler cannot be called until the message box is dismissed, no matter how much time has elapsed. (MessageBox.Show is a blocking operation.) This is why DiscoveredButton_Click in Listing 33.2 pauses the video first.

When I first wrote Subservient Cat, I called MediaElement’s Stop method inside OnNavigatedFrom because I was worried about the performance impact of unnecessarily playing video while the instructions page is shown and the main page is on the back stack. However, this is unnecessary because MediaElement is automatically paused when a page navigates away. If you don’t want this behavior (perhaps because you want to hear the audio from the video continue while other pages are shown), the MediaElement must be attached to the frame rather than to a specific page.

MediaElement and Files in Isolated Storage

If you want to play a video from isolated storage (presumably because your app downloaded it and then saved it there), you can call MediaElement’s SetSource method that expects a stream rather than a URI. With this, you can pass an appropriate IsolatedStorageFileStream.

The Finished Product

Subservient Cat (Video)

 

Alphabet Flashcards (Filmstrip-Style Swiping)

Someone recently asked me how to build a flashcard app where the user could swipe between cards, much like when navigating a strip of photos in the Pictures hub or in the Facebook app. For lack of a better term, I’m referring to this as a filmstrip user experience.

It occurred to me that there’s currently no simple panel or other element for performing this type of interaction. A simple scroll viewer doesn’t work as-is because you want each item to be “magnetic” and not allow the view to rest in an unaligned position.

Although the pivot and panorama controls are much more feature-filled than what’s required for this task, they provide the easiest way to create this kind of user interface. You just need to hide the title and header and make some layout tweaks. A pivot is better suited for dynamic items, thanks to its LoadingPivotItem and UnloadingPivotItem events, but a panorama is a bit better suited for faithfully mimicking a filmstrip, as it enables you to see the next or previous item in the midst of a swipe.

Therefore, Alphabet Flashcards uses a panorama to provide filmstrip-style browsing of a set of 26 flashcards, one for each letter of the alphabet. A parent can use this with a toddler to practice learning letters.

The User Interface

Alphabet Flashcards uses a single panorama-filled page with 27 panorama items: one for each letter in the alphabet and one that acts like a title page. Figure 28.1 shows the experience of swiping from the first item (the title page) to the second item (the letter A).

Swiping from the first panorama item to the second panorama item.
FIGURE 28.1 Swiping from the first panorama item to the second panorama item.

To get the illusion of a full-screen filmstrip experience, the panorama used by this page and its items do not use any title or headers. The items also use a slight negative top margin to consume space that would be wasted.

The right-most 48 pixels of the control are reserved for a 12-pixel margin between items and a 36-pixel preview of the next item’s left side. Because we don’t want the preview behavior in this app, we simply ensure the content in each item has a 36-pixel left margin. Besides keeping the next item off-screen until swiping begins, this also keeps the content centered. It does, however, force the content to have a maximum width of 384 in the portrait orientation (480 – 48×2). Figure 28.2 demonstrates this more clearly by giving the panorama an orange background, the first item a green background, the second item a blue background, and the third item a purple background.

Listing 28.1 contains the XAML for this page.

FIGURE 28.2 The same sequence as in Figure 28.1, but showing where each item—and its inner content—begins and ends.
FIGURE 28.2 The same sequence as in Figure 28.1, but showing where each item—and its inner content—begins and ends.

LISTING 28.1 MainPage.xaml—The User Interface for Alphabet Flashcards’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:controls=”clr-namespace:Microsoft.Phone.Controls;
➥assembly=Microsoft.Phone.Controls”
SupportedOrientations=”Portrait”>
<Grid Background=”White”>
<controls:Panorama x:Name=”Panorama”>
<controls:Panorama.ItemTemplate>
<DataTemplate>
<Grid Margin=”36,-24,0,0”>
<Image Source=”{Binding}”/>
</Grid>
</DataTemplate>
</controls:Panorama.ItemTemplate>
<controls:Panorama.HeaderTemplate>
<!– Make sure the header is empty –>
<DataTemplate><Canvas/></DataTemplate>
</controls:Panorama.HeaderTemplate>
</controls:Panorama>
</Grid>
</phone:PhoneApplicationPage>

[/code]

  • Rather than giving the panorama 27 items in XAML, the code-behind sets its ItemsSource to an array of image URI strings. The panorama uses an item template to render each image inside a grid with a margin needed to get the effect shown in Figure 28.1.
  • When a panorama contains something other than a PanoramaItem control, such as the strings used by this app, each item’s main content and header displays the item. Therefore, Listing 28.1 must explicitly set each item’s HeaderTemplate to something blank to avoid each URI being rendered in a text block on top of each image.
  • The images used in this app are marked with a Build Action of Resource, so the panorama does not appear until the images are ready.

The Code-Behind

Listing 28.2 contains the code-behind for the main page. Besides filling the panorama with items, it persists and restores the selected item, so the app can resume where it left off.

LISTING 28.2 MainPage.xaml.cs—The Code-Behind for Alphabet Flashcards’Main Page

[code]

using System.Windows.Navigation;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
// Persist the selected item
Setting<int> selectedIndex = new Setting<int>(“SelectedIndex”, 0);
public MainPage()
{
InitializeComponent();
this.Panorama.ItemsSource = new string[] {
“Images/title.png”,
“Images/a.png”,
“Images/b.png”,
“Images/c.png”,

“Images/x.png”,
“Images/y.png”,
“Images/z.png”
};
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Restore the selected item
this.Panorama.DefaultItem = this.Panorama.Items[this.selectedIndex.Value];
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Remember the selected item
this.selectedIndex.Value = this.Panorama.SelectedIndex;
}
}
}

[/code]

Here, DefaultItem is used exactly how it was designed to be used. The problems with DefaultItem discussed in the preceding chapter aren’t problematic in this app because there is no distinguishable panorama title or background.

The Finished Product

Alphabet Flashcards (Filmstrip-Style Swiping)

Baby Name Eliminator (Local Databases & Embedded Resources)

Baby Name Eliminator provides the perfect technique for Type A personalities to name their babies. (It’s the technique my wife and I used to name our two sons!) Rather than trying to brainstorm names and worrying that you’re missing the perfect one, this app enables you to use the process of elimination to name your baby!

Baby Name Eliminator starts with a massive database of essentially every name ever used in the United States: 36,065 boy names and 60,438 girl names. After you choose a gender, the app enables you to quickly narrow down the list with a variety of filters. These filters are based on the popularity of each name, its starting/ending letter, and the year the name was first in use. Once you’ve finished filtering the list, you can eliminate names one-by-one until your decision is made.

When naming our sons, we went through several rounds, eliminating names that were obviously bad and leaving names that we had any hesitation about. Once we got down to about 20 names, my wife and I each picked our top 5 choices. With our first son, we only had one name in common, so our decision was made! If you and your spouse both have a Windows phone, independently eliminating names can be a fun way to come up with a final list of candidate names.

So where does this massive database of names come from? The Social Security Administration, which provides data about almost every first name used in a Social Security card application from 1880 to the present. There are a few caveats to this list:

  • For privacy reasons, only names used at least five times in any given year are included.
  • One-character names are excluded.
  • Many people born before 1937 never applied for a Social Security card, so data from these years is spotty.
  • Distinct spellings of the same name are treated as different names.
  • The data is raw and uncorrected. Sometimes the sex on an application is incorrect, causing some boy names to show up in the girl names list and vice versa. In addition, some names are recorded as “Unknown,” “Unnamed,” or “Baby.” Restricting your list to the top 1,000 or so names in any year generally gets rid of such artifacts.

To enable its filtering, this app makes use of two local databases—one for boy names and one for girl names.

Working with Local Databases

The lack of local database support in Windows Phone 7 is one of its more publicized shortcomings. Apps are encouraged to work with server-side databases instead, but this adds extra burden for developers and extra hassle for users (latency, a working data connection, and potential data charges). Fortunately, several third-party database options exist. My favorite is an open-source port of SQLite for Windows Phone 7 created by Dan Ciprian Ardelean. You can read about it at http://sviluppomobile.blogspot.com/ 2010/03/sqlite-for-wp-7-series-proof-of-concept.html and get the latest version (at the time of this writing) at http://www.neologics.eu/Dan/WP7_Sqlite_20.09.2010.zip. This includes C# source code and a Community.CsharpSqlite.WP.dll assembly that you can reference in your project. It’s certainly not bug-free, but it works quite well for a number of scenarios (such as the needs of this app).

SQLite for Windows Phone 7 reads from and writes to database files in isolated storage. If you want to ship a database with your app that’s already filled with data, you can include the database file in your project with a Build Action of Content. At run-time, your app can retrieve the file then save it to isolated storage before its first use of SQLite.

How can I create a .db file that contains the database I want to ship with my app?

I followed the somewhat-cumbersome approach of writing a Windows Phone app that

  1. Uses SQLite to generate the database, executing CREATE TABLE and INSERT commands
  2. Retrieves the raw bytes from the .db file saved by SQLite to isolated storage, using thenormal isolated storage APIs
  3. Copies the bytes from the Visual Studio debugger as a Base64-encoded string and saves them to the needed .db file with a separate (desktop) program that decodes the string

Listing 24.1 contains a DatabaseHelper class used by Baby Name Eliminator that handles all interaction with the two SQLite databases included in the app.

LISTING 24.1 DatabaseHelper.cs—A Class That Wraps SQLite

[code]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows;
using System.Windows.Resources;
using SQLiteClient;
namespace WindowsPhoneApp
{
public class DatabaseHelper
{
// The name of the file included as content in this project,
// also used as the isolated storage filename
public static string DatabaseName { get; set; }
// “Load” the database. If the file does not yet exist in isolated storage,
// copy it from the original file. If the file already exists,
// this is a no-op.
public static void LoadAsync(Action callback)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate(object sender, DoWorkEventArgs e)
{
if (!HasLoadedBefore)
{
StreamResourceInfo info = Application.GetResourceStream(
new Uri(DatabaseName, UriKind.Relative));
using (info.Stream)
SaveFile(DatabaseName, info.Stream);
}
if (callback != null)
callback();
};
worker.RunWorkerAsync();
}
// Retrieve a single value from the database
public static void ExecuteScalar(string command, Action<object> onSuccess,
Action<Exception> onError = null)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate(object sender, DoWorkEventArgs e)
{
try
{
object result = null;
using (SQLiteConnection db = new SQLiteConnection(DatabaseName))
{
db.Open();
SQLiteCommand c = db.CreateCommand(command);
result = c.ExecuteScalar();
}
if (onSuccess != null)
onSuccess(result);
}
catch (Exception ex)
{
if (onError != null)
onError(ex);
}
};
worker.RunWorkerAsync();
}
// Retrieve a collection of items from the database
public static void ExecuteQuery<T>(string command,
Action<IEnumerable<T>> onSuccess,
Action<Exception> onError = null) where T : new()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate(object sender, DoWorkEventArgs e)
{
try
{
IEnumerable<T> result = null;
List<T> copy = new List<T>();
using (SQLiteConnection db = new SQLiteConnection(DatabaseName))
{
db.Open();
SQLiteCommand c = db.CreateCommand(command);
result = c.ExecuteQuery<T>();
// Copy the data, because enumeration only
// works while the connection is open
copy.AddRange(result);
}
if (onSuccess != null)
onSuccess(copy);
}
catch (Exception ex)
{
if (onError != null)
onError(ex);
}
};
worker.RunWorkerAsync();
}
public static bool HasLoadedBefore
{
get
{
using (IsolatedStorageFile userStore =
IsolatedStorageFile.GetUserStoreForApplication())
return userStore.FileExists(DatabaseName);
}
}
// Save a stream to isolated storage
static void SaveFile(string filename, Stream data)
{
using (IsolatedStorageFile userStore =
IsolatedStorageFile.GetUserStoreForApplication())
using (IsolatedStorageFileStream stream = userStore.CreateFile(filename))
{
// Get the bytes
byte[] bytes = new byte[data.Length];
data.Read(bytes, 0, bytes.Length);
// Write the bytes to the new stream
stream.Write(bytes, 0, bytes.Length);
}
}
}
}

[/code]

  • To enable a responsive user interface while expensive database operations are conducted, interaction with SQLite is done on a background thread with the help of BackgroundWorker, and success/failure is communicated via callbacks.
  • The command strings passed to ExecuteScalar and ExecuteQuery can be SQL commands like SELECT COUNT(*) FROM table.
  • ExecuteQuery is a generic method whose generic argument (T) must be a class with a property corresponding to each column selected in the query.

Application.GetResourceStream works with files included in your project with a Build Action of Content or with a Build Action of Resource. For the latter case, the passed-in URI must have the following syntax:

/dllName;component/pathAndFilename

Note that dllName can refer to any DLL inside the .xap file, as long as it contains the requested resource. It should not contain the .dll suffix.

For this app, the DatabaseName string would look as follows for the database of boy names (Boys.db) included in the root of the project as a resource rather than content:

/WindowsPhoneApp;component/Boys.db

However, if this were done, Listing 24.1’s use of SaveFile would have to change, because the DatabaseName string would no longer be a valid filename for isolated storage.

Application.GetResourceStream Versus Assembly.GetManifestResourceStream

You might stumble across the Assembly.GetManifestResourceStream API as a way to read files included with your app.This works, but only for files marked with a Build Action of Embedded Resource (not Resource).Using this in Listing 24.1 instead of Application.GetResourceStream would look as follows:

[code]

if (!HasLoadedBefore)
{
using (Stream stream = typeof(DatabaseHelper).
Assembly.GetManifestResourceStream(DatabaseName))
SaveFile(DatabaseName, stream);
}

[/code]

However, the string passed to GetManifestResourceStream has its own unique syntax: dllName.filename, where dllName is the name of the DLL containing the embedded resource. That’s because the C# compiler automatically prepends the DLL name (minus the .dll extension) to the filename when naming each embedded resource. (You can see these names by opening a DLL in a tool such as .NET Reflector.) For this app, the two valid strings would be “WindowsPhoneApp.Boys.db” and “WindowsPhoneApp.Girls.db”.

There’s no significant reason to use this approach rather than the more flexible Application. GetResourceStream. Using GetResourceStream with files included as content is generally preferable compared to either scheme with files embedded as resources, because resources increase the size of DLLs, and that can increase an app’s load time.

The Filter Page

Rather than examine this app’s main page, which you can view in the included source code, we’ll examine the filter page that makes use of the DatabaseHelper class. The filter page, shown in Figure 24.1, displays how many names are in your list then enables you to filter it further with several options that map to SQL queries performed on the database. (The choice of boy names versus girl names is done previously on the main page.)

FIGURE 24.1 The filter page supports five different types of filters.
FIGURE 24.1 The filter page supports five different types of filters.

Each button reveals a dialog or other display, shown in Figure 24.2, that enables the user to control each relevant filter. Tapping the count of names reveals the actual list of names, as shown in Figure 24.3. This list doesn’t enable interactive elimination, however, as that is handled on the main page.

FIGURE 24.2 The result of tapping each button on the filter page.
FIGURE 24.2 The result of tapping each button on the filter page.
FIGURE 24.3 Previewing the filtered list of names.
FIGURE 24.3 Previewing the filtered list of names.

Listing 24.2 contains the XAML for the filter page.

LISTING 24.2 FilterPage.xaml—The User Interface for Baby Name Eliminator’s Filter Page

[code]

<phone:PhoneApplicationPage x:Name=”Page”
x:Class=”WindowsPhoneApp.FilterPage”
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 Background=”Transparent”>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard header –>
<StackPanel Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock Text=”BABY NAME ELIMINATOR”
Style=”{StaticResource PhoneTextTitle0Style}”/>
<TextBlock Text=”apply filters”
Style=”{StaticResource PhoneTextTitle1Style}”/>
</StackPanel>
<ScrollViewer Grid.Row=”1”>
<Grid Margin=”12,0”>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<!– The current number of names –>
<StackPanel Background=”Transparent” local:Tilt.IsEnabled=”True”
MouseLeftButtonUp=”Preview_Click”>
<TextBlock Text=”Current # of names (tap to preview):”
HorizontalAlignment=”Center”
Style=”{StaticResource LabelStyle}”/>
<TextBlock x:Name=”NumberTextBlock” Text=”0” Margin=”0,-16,0,0”
HorizontalAlignment=”Center”
FontSize=”{StaticResource PhoneFontSizeExtraExtraLarge}”/>
</StackPanel>
<!– Progress indicator while a query is running –>
<Grid x:Name=”ProgressPanel”>
<Rectangle Fill=”{StaticResource PhoneBackgroundBrush}” Opacity=”.9”/>
<ProgressBar x:Name=”ProgressBar” VerticalAlignment=”Top”/>
<TextBlock x:Name=”ProgressText” TextWrapping=”Wrap”
HorizontalAlignment=”Center”
VerticalAlignment=”Top” Margin=”0,60,0,0” Text=”Loading”/>
</Grid>
<!– The five filter buttons –>
<ToggleButton x:Name=”RankMaxButton” Grid.Row=”1”
Content=”eliminate low-ranked names”
local:Tilt.IsEnabled=”True” Click=”RankMaxButton_Click”/>
<ToggleButton x:Name=”NameStartButton” Grid.Row=”2”
Content=”eliminate names starting with…”
local:Tilt.IsEnabled=”True” Click=”NameStartButton_Click”/>
<ToggleButton x:Name=”NameEndButton” Grid.Row=”3”
Content=”eliminate names ending with…”
local:Tilt.IsEnabled=”True” Click=”NameEndButton_Click”/>
<ToggleButton x:Name=”YearMaxButton” Grid.Row=”4”
Content=”eliminate modern names”
local:Tilt.IsEnabled=”True” Click=”YearMaxButton_Click”/>
<ToggleButton x:Name=”YearMinButton” Grid.Row=”5”
Content=”eliminate old-fashioned names”
local:Tilt.IsEnabled=”True” Click=”YearMinButton_Click”/>
<!– A user control that displays the letter grid in a popup –>
<local:LetterPicker x:Name=”LetterPicker”
Page=”{Binding ElementName=Page}”
Closed=”LetterPicker_Closed”/>
</Grid>
</ScrollViewer>
<!– Eliminate low-ranked names dialog –>
<local:Dialog x:Name=”RankMaxDialog” Grid.RowSpan=”2” Closed=”Dialog_Closed”>
<local:Dialog.InnerContent>
<StackPanel>
<TextBlock Text=”…” TextWrapping=”Wrap” Margin=”11,5,0,-5”/>
<TextBox MaxLength=”5” InputScope=”Number”
Text=”{Binding Result, Mode=TwoWay}”/>
<TextBlock Text=”Enter a number, or leave blank to clear this filter.”
TextWrapping=”Wrap” Margin=”11,-10,0,-10”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
</StackPanel>
</local:Dialog.InnerContent>
</local:Dialog>
<!– Eliminate modern names dialog –>
<local:Dialog x:Name=”YearMaxDialog” Grid.RowSpan=”2” Closed=”Dialog_Closed”>
<local:Dialog.InnerContent>
<StackPanel>
<TextBlock TextWrapping=”Wrap” Margin=”11,5,0,-5”>

</TextBlock>
<TextBox MaxLength=”4” InputScope=”Number”
Text=”{Binding Result, Mode=TwoWay}”/>
<TextBlock Text=”…” TextWrapping=”Wrap” Margin=”11,-10,0,-10”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
</StackPanel>
</local:Dialog.InnerContent>
</local:Dialog>
<!– Eliminate old-fashioned names dialog –>
<local:Dialog x:Name=”YearMinDialog” Grid.RowSpan=”2” Closed=”Dialog_Closed”>
<local:Dialog.InnerContent>
<StackPanel>
<TextBlock TextWrapping=”Wrap” Margin=”11,5,0,-5”>

</TextBlock>
<TextBox MaxLength=”4” InputScope=”Number”
Text=”{Binding Result, Mode=TwoWay}”/>
<TextBlock Text=”…” TextWrapping=”Wrap” Margin=”11,-10,0,-10”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
</StackPanel>
</local:Dialog.InnerContent>
</local:Dialog>
<!– The list of names shown when tapping the current number –>
<Grid x:Name=”PreviewPane” Grid.RowSpan=”2” Visibility=”Collapsed”>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<Rectangle Grid.RowSpan=”2” Fill=”{StaticResource PhoneChromeBrush}”
Opacity=”.9”/>
<StackPanel Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock x:Name=”PreviewHeader”
Style=”{StaticResource PhoneTextTitle0Style}”/>
</StackPanel>
<ListBox Grid.Row=”1” x:Name=”PreviewListBox” Margin=”24,0,0,0”/>
</Grid>
</Grid>
</phone:PhoneApplicationPage>

[/code]

  • The five filter buttons are toggle buttons whose IsChecked state is managed by code-behind. If a filter is active, its corresponding button is checked (highlighted) so the user can see this without tapping every button and double-checking its filter settings.
  • The progress bar and related user interface, shown while a query is executing on a background thread, is shown in Figure 24.4. Because it does not occupy the whole screen, it enables the user to continue working if he or she doesn’t care to wait for the current count of names.
FIGURE 24.4 Showing progress while a database query executes on a background thread.
FIGURE 24.4 Showing progress while a database query executes on a background thread.

Listing 24.3 contains the code-behind for the filter page.

LISTING 24.3 FilterPage.xaml.cs—The Code-Behind for Baby Name Eliminator’s Filter Page

[code]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class FilterPage : PhoneApplicationPage
{
public FilterPage()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
RefreshCount();
RefreshButtons();
}
protected override void OnBackKeyPress(CancelEventArgs e)
{
base.OnBackKeyPress(e);
// If a dialog, letter picker, or preview pane is open,
// close it instead of leaving the page
if (this.RankMaxDialog.Visibility == Visibility.Visible)
{
e.Cancel = true;
this.RankMaxDialog.Hide(MessageBoxResult.Cancel);
}
else if (this.YearMaxDialog.Visibility == Visibility.Visible)
{
e.Cancel = true;
this.YearMaxDialog.Hide(MessageBoxResult.Cancel);
}
else if (this.YearMinDialog.Visibility == Visibility.Visible)
{
e.Cancel = true;
this.YearMinDialog.Hide(MessageBoxResult.Cancel);
}
else if (this.PreviewPane.Visibility == Visibility.Visible)
{
e.Cancel = true;
this.PreviewPane.Visibility = Visibility.Collapsed;
}
}
void RefreshCount()
{
// Choose one of the included databases: boy names or girl names
if (Settings.IsBoy.Value.Value)
DatabaseHelper.DatabaseName = “Boys.db”;
else
DatabaseHelper.DatabaseName = “Girls.db”;
if (!DatabaseHelper.HasLoadedBefore)
{
ShowProgress(“Preparing database for the first time…”);
this.RankMaxButton.IsEnabled = false;
this.NameStartButton.IsEnabled = false;
this.NameEndButton.IsEnabled = false;
this.YearMaxButton.IsEnabled = false;
this.YearMinButton.IsEnabled = false;
}
DatabaseHelper.LoadAsync(delegate()
{
// The callback is called on a background thread, so transition back
// to the main thread for manipulating UI
this.Dispatcher.BeginInvoke(delegate()
{
ShowProgress(“Counting names…”);
this.RankMaxButton.IsEnabled = true;
this.NameStartButton.IsEnabled = true;
this.NameEndButton.IsEnabled = true;
this.YearMaxButton.IsEnabled = true;
this.YearMinButton.IsEnabled = true;
// Execute a query
DatabaseHelper.ExecuteScalar(“SELECT COUNT(*) FROM Names “ +
Settings.BuildQuerySuffix(), delegate(object result)
{
// The callback is called on a background thread, so transition back
// to the main thread for manipulating UI
this.Dispatcher.BeginInvoke(delegate()
{
HideProgress();
this.NumberTextBlock.Text = ((int)result).ToString(“N0”);
});
});
});
});
}
void RefreshButtons()
{
// Check (highlight) any button whose filter is active
this.RankMaxButton.IsChecked =
Settings.RankMax.Value != Settings.RankMax.DefaultValue;
this.NameStartButton.IsChecked =
Settings.ExcludedStartingLetters.Value.Count > 0;
this.NameEndButton.IsChecked =
Settings.ExcludedEndingLetters.Value.Count > 0;
this.YearMaxButton.IsChecked =
Settings.YearMax.Value != Settings.YearMax.DefaultValue;
this.YearMinButton.IsChecked =
Settings.YearMin.Value != Settings.YearMin.DefaultValue;
}
void Preview_Click(object sender, MouseButtonEventArgs e)
{
this.PreviewHeader.Text = “LOADING…”;
this.PreviewListBox.ItemsSource = null;
this.PreviewPane.Visibility = Visibility.Visible;
// Choose one of the included databases: boy names or girl names
if (Settings.IsBoy.Value.Value)
DatabaseHelper.DatabaseName = “Boys.db”;
else
DatabaseHelper.DatabaseName = “Girls.db”;
DatabaseHelper.LoadAsync(delegate()
{
// It’s okay to execute this on the background thread
DatabaseHelper.ExecuteQuery<Record>(“SELECT Name FROM Names “ +
Settings.BuildQuerySuffix(), delegate(IEnumerable<Record> result)
{
// Transition back to the main thread for manipulating UI
this.Dispatcher.BeginInvoke(delegate()
{
this.PreviewHeader.Text = “PRESS BACK WHEN DONE”;
this.PreviewListBox.ItemsSource = result;
});
});
});
}
void ShowProgress(string message)
{
this.ProgressText.Text = message;
this.ProgressBar.IsIndeterminate = true;
this.ProgressPanel.Visibility = Visibility.Visible;
}
void HideProgress()
{
this.ProgressPanel.Visibility = Visibility.Collapsed;
this.ProgressBar.IsIndeterminate = false; // Avoid a perf problem
}
// A click handler for each of the five filter buttons
void RankMaxButton_Click(object sender, RoutedEventArgs e)
{
if (Settings.RankMax.Value != null)
RankMaxDialog.Result = Settings.RankMax.Value.Value;
RankMaxDialog.Show();
}
void NameStartButton_Click(object sender, RoutedEventArgs e)
{
this.LetterPicker.SetBinding(LetterPicker.ExcludedLettersProperty,
new Binding { Path = new PropertyPath(“Value”),
Source = Settings.ExcludedStartingLetters,
Mode = BindingMode.TwoWay });
this.LetterPicker.ShowPopup();
}
void NameEndButton_Click(object sender, RoutedEventArgs e)
{
this.LetterPicker.SetBinding(LetterPicker.ExcludedLettersProperty,
new Binding { Path = new PropertyPath(“Value”),
Source = Settings.ExcludedEndingLetters,
Mode = BindingMode.TwoWay });
this.LetterPicker.ShowPopup();
}
void YearMaxButton_Click(object sender, RoutedEventArgs e)
{
if (Settings.YearMax.Value != null)
YearMaxDialog.Result = Settings.YearMax.Value.Value;
YearMaxDialog.Show();
}
void YearMinButton_Click(object sender, RoutedEventArgs e)
{
if (Settings.YearMin.Value != null)
YearMinDialog.Result = Settings.YearMin.Value.Value;
YearMinDialog.Show();
}
// Two handlers for the dialog or letter picker being closed
void LetterPicker_Closed(object sender, EventArgs e)
{
RefreshCount();
RefreshButtons();
}
void Dialog_Closed(object sender, MessageBoxResultEventArgs e)
{
if (e.Result == MessageBoxResult.OK)
{
// Update or clear a setting, depending on which dialog was just closed
int result;
if (sender == RankMaxDialog)
{
if (RankMaxDialog.Result != null &&
int.TryParse(RankMaxDialog.Result.ToString(), out result))
Settings.RankMax.Value = result;
else
Settings.RankMax.Value = null;
}
if (sender == YearMaxDialog)
{
if (YearMaxDialog.Result != null &&
int.TryParse(YearMaxDialog.Result.ToString(), out result))
Settings.YearMax.Value = (short)result;
else
Settings.YearMax.Value = null;
}
if (sender == YearMinDialog)
{
if (YearMinDialog.Result != null &&
int.TryParse(YearMinDialog.Result.ToString(), out result))
Settings.YearMin.Value = (short)result;
else
Settings.YearMin.Value = null;
}
// Only bother refreshing the count if the dialog result is OK
RefreshCount();
}
// Refresh buttons when the dialog is closed for any reason,
// to undo automatic check-when-tapped
RefreshButtons();
}
}
}

[/code]

  • This project includes two databases (Boys.db and Girls.db) that have an identical schema. They contain a single table called Names with three columns: Name, BestRank (its best single-year ranking), and FirstYear (the first year the name appeared in Social Security data).
  • The query to refresh the count of names is “SELECT COUNT(*) FROM Names” with a WHERE clause based on settings whose values are determined by the filters. The settings and the BuildQuerySuffix method are defined in Listing 24.4.
  • The query to display the list of actual names is “SELECT Name FROM Names” with the same WHERE clause. The Record class used with ExecuteQuery is therefore a class with a single string Name property:

    [code]
    public class Record
    {
    public string Name { get; set; }
    public override string ToString()
    {
    return this.Name;
    }
    }
    [/code]
    The ToString method enables the collection of Records to be used as the data source for the preview list box without any item template, as the default ToStringin- a-text-block rendering is sufficient.

  • Just like the date picker in the preceding chapter, this app leverages two-way data binding with each letter picker.

LISTING 24.4 Settings.cs—The Settings Class for Baby Name Eliminator

[code]

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public static class Settings
{
// Step 1: Gender
public static readonly Setting<bool?> IsBoy =
new Setting<bool?>(“IsBoy”, null);
// Step 2: Filters
public static readonly Setting<int?> RankMax =
new Setting<int?>(“RankMax”, null);
public static readonly Setting<List<char>> ExcludedStartingLetters =
new Setting<List<char>>(“IncludedStartingLetters”, new List<char>());
public static readonly Setting<List<char>> ExcludedEndingLetters =
new Setting<List<char>>(“ExcludedEndingLetters”, new List<char>());
public static readonly Setting<short?> YearMax =
new Setting<short?>(“YearMax”, null);
public static readonly Setting<short?> YearMin =
new Setting<short?>(“YearMin”, null);
// Step 3: Elimination
public static readonly Setting<ObservableCollection<string>> FilteredList =
new Setting<ObservableCollection<string>>(“FilteredList”, null);
public static readonly Setting<double> ScrollPosition =
new Setting<double>(“ScrollPosition”, 0);
// Orientation lock for the main page
public static readonly Setting<SupportedPageOrientation>
SupportedOrientations = new Setting<SupportedPageOrientation>(
“SupportedOrientations”, SupportedPageOrientation.PortraitOrLandscape);
// Build up a WHERE clause if any filters have been chosen
public static string BuildQuerySuffix()
{
List<string> conditions = new List<string>();
if (Settings.RankMax.Value != null)
conditions.Add(“ BestRank <= “ + Settings.RankMax.Value.Value);
foreach (char c in Settings.ExcludedStartingLetters.Value)
conditions.Add(“ NOT Name LIKE ‘“ + c + “%’”);
foreach (char c in Settings.ExcludedEndingLetters.Value)
conditions.Add(“ NOT Name LIKE ‘%” + c + “‘“);
if (Settings.YearMax.Value != null)
conditions.Add(“ FirstYear <= “ + Settings.YearMax.Value.Value);
if (Settings.YearMin.Value != null)
conditions.Add(“ FirstYear >= “ + Settings.YearMin.Value.Value);
if (conditions.Count == 0)
return “”;
else
{
StringBuilder whereClause = new StringBuilder(“WHERE “);
whereClause.Append(conditions[0]);
for (int i = 1; i < conditions.Count; i++)
whereClause.Append(“ AND “ + conditions[i]);
return whereClause.ToString();
}
}
}
}

[/code]

The Finished Product

Baby Name Eliminator (Local Databases & Embedded Resources)

 

Fake Call (Resources & Styles)

The Fake Call app makes your phone appear to receive an incoming call at a time you specify. You can use this as an excuse to get out of a bad date or an otherwise-unpleasant situation. Simply pretend to answer the fake call, make up some plausible story about needing to leave as a result of the phone call, and leave!

This app has three pages:

  • The main page (for your eyes only), which enables you to customize when the fake call will occur and who is fake-calling you—in case the victim of your scheme catches a glimpse of your screen.
  • The incoming-call page, which stays blank until it’s time for the incoming call to appear.
  • The call-in-progress page, which mimics the active phone call once you press the “answer” button.

Unlike the preceding app, this intentionally does not use the page transition animation when navigating from one page to another, because that would interfere with the illusion that the real Phone app is being used. In this app, multiple pages are used just as a nice way to structure the code rather than as a metaphor that benefits users.

The Main Page

Fake Call’s main page, pictured in Figure 9.1

FIGURE 9.1 The main page is a simple form to fill out, much like the main page in the “In Case of Emergency”app.
FIGURE 9.1 The main page is a simple form to fill out, much like the main page in the “In Case of Emergency”app.

The User Interface

Listing 9.1 contains the XAML for the 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:local=”clr-namespace:WindowsPhoneApp”
xmlns:toolkit=”clr-namespace:Microsoft.Phone.Controls;
➥assembly=Microsoft.Phone.Controls.Toolkit”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”PortraitOrLandscape”
shell:SystemTray.IsVisible=”True”>
<!– Add some items to the page’s resource dictionary –>
<phone:PhoneApplicationPage.Resources>
<!– A margin for the standard header –>
<Thickness x:Key=”PhoneTitlePanelMargin”>24,16,0,12</Thickness>
<!– The tweaked application title style –>
<Style x:Key=”PhoneTextTitle0Style” TargetType=”TextBlock”>
<Setter Property=”FontFamily”
Value=”{StaticResource PhoneFontFamilySemiBold}”/>
<Setter Property=”Foreground”
Value=”{StaticResource PhoneForegroundBrush}”/>
<Setter Property=”FontSize” Value=”{StaticResource PhoneFontSizeMedium}”/>
<Setter Property=”Margin” Value=”-1,0,0,0”/>
</Style>
<!– The tweaked page title style –>
<Style x:Key=”PhoneTextTitle1Style” TargetType=”TextBlock”>
<Setter Property=”FontFamily”
Value=”{StaticResource PhoneFontFamilySemiLight}”/>
<Setter Property=”Foreground”
Value=”{StaticResource PhoneForegroundBrush}”/>
<Setter Property=”FontSize”
Value=”{StaticResource PhoneFontSizeExtraExtraLarge}”/>
<Setter Property=”Margin” Value=”-3,-10,0,0”/>
</Style>
<!– A style for text blocks that act as a label for a text box –>
<Style x:Key=”LabelStyle” TargetType=”TextBlock”>
<Setter Property=”Foreground” Value=”{StaticResource PhoneSubtleBrush}”/>
<Setter Property=”Margin” Value=”24,17,24,-5”/>
</Style>
</phone:PhoneApplicationPage.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard header, with some tweaks –>
<StackPanel Grid.Row=”0” Margin=”{StaticResource PhoneTitlePanelMargin}”>
<TextBlock Text=”FAKE CALL” Style=”{StaticResource PhoneTextTitle0Style}”/>
<TextBlock Text=”call settings”
Style=”{StaticResource PhoneTextTitle1Style}”/>
</StackPanel>
<!– Scrollable pane (for the sake of the landscape orientation) –>
<ScrollViewer Grid.Row=”1”>
<!– A button and three text block / text box pairs stacked vertically –>
<StackPanel>
<Button Content=”wait for call” HorizontalAlignment=”Left”
Margin=”12,13,0,18” Click=”WaitForCallButton_Click”/>
<TextBlock Text=”Time to receive call”
Style=”{StaticResource LabelStyle}”/>
<toolkit:TimePicker x:Name=”TimePicker” local:Tilt.IsEnabled=”True”
ValueChanged=”TimePicker_ValueChanged”
Margin=”{StaticResource PhoneHorizontalMargin}”/>
<TextBlock Text=”Incoming phone number”
Style=”{StaticResource LabelStyle}”/>
<!– Use the Number input scope so we also get parentheses and a dash –>
<TextBox x:Name=”PhoneNumberTextBox” InputScope=”Number”
Margin=”{StaticResource PhoneHorizontalMargin}”
GotFocus=”TextBox_GotFocus” KeyDown=”TextBox_KeyDown”/>
<TextBlock Text=”Carrier name” Style=”{StaticResource LabelStyle}”/>
<TextBox x:Name=”CarrierTextBox” InputScope=”PersonalFullName”
Margin=”{StaticResource PhoneHorizontalMargin}”
GotFocus=”TextBox_GotFocus” KeyDown=”TextBox_KeyDown”/>
</StackPanel>
</ScrollViewer>
</Grid>
</phone:PhoneApplicationPage>

[/code]

Notes:

  • This page supports all orientations for the sake of text entry.
  • This page appropriately uses a time picker rather than a simple text box for choosing the call time. Although it initially looks like a text box, it presents a rich fullpage interface when tapped that matches the phone’s built-in apps, as pictured in Figure 9.2. The time picker ships with the Silverlight for Windows Phone Toolkit, so you must reference the toolkit’s assembly and namespace to use it.
  • The PhoneNumberTextBox text box uses the Number input scope rather than TelephoneNumber because its text is displayed as-is on subsequent pages. This enables the user to type parentheses and a dash to get the optimal display. The Number input scope includes these keys, but TelephoneNumber does not.
  • The most important aspect of this listing is its use of resources and styles, covered next.
FIGURE 9.2 The time picker makes it easy to choose a time with taps and swipes.
FIGURE 9.2 The time picker makes it easy to choose a time with taps and swipes.

The time picker requires two specific images to be included in your project!

As with the date picker, you must include two images from the Silverlight for Windows Phone Toolkit in your project (and mark them with a Build Action of Content).These images are ApplicationBar.Check.png and ApplicationBar.Cancel.png, and they must be placed in a folder called Toolkit.Content in the root of your project.

Resources

Windows Phone apps can have two types of resources. One type, often called binary resources, consists of things like image files, audio/video files, and font files. Sometimes these are embedded into your DLL (when the Build Action is set to Resource) and sometimes these are placed as loose files inside your .xap file (when the Build Action is set to Content). The choice of how to package binary resources is sometimes dictated by their consumer (such as the application bar requiring loose files), and sometimes dictated by how you wish to localize the resources in order support multiple languages and regions. As you’ve seen in previous chapters, various elements support referencing binary resources via URIs.

Defining XAML Resources

The XAML files in this app, such as MainPage.xaml in Listing 9.1, make use of the other type of resources. They are often called XAML resources because they are typically defined in XAML, but this book generally refers to them as simply resources, using the term binary resources to refer to the other kind.

A resource is an arbitrary object stored in an element’s Resources property. Such objects can be anything—a number, a brush, or a complex object such as a style. The first resource defined in Listing 9.1 is a thickness, the data type used by margins and padding:

[code]<Thickness x:Key=”PhoneTitlePanelMargin”>24,16,0,12</Thickness>[/code]

The point of defining a resource is to separate and consolidate styling information, much like using Cascading Style Sheets (CSS) to control colors and styles in a webpage rather than hard-coding them on individual elements. The Resources property is a dictionary (usually called a resource dictionary), so adding a child element is equivalent to adding a new key/value pair to the dictionary in C#. The value is the element, and the key is the string specified via the x:Key attribute. Everything placed in a resource dictionary must have a key.

Referencing XAML Resources

Resources are referenced with the familiar StaticResource syntax. Each resource is often referenced by more than one element, although the thickness resource with the PhoneTitlePanelMargin key happens to be referenced only once:

[code]

<StackPanel Grid.Row=”0” Margin=”{StaticResource PhoneTitlePanelMargin}”>

</StackPanel>

[/code]

The identifier specified along with StaticResource is the key for the desired resource. Because the object with the PhoneTitlePanelMargin key is the right data type for the Margin property, setting it this way is valid and behaves exactly the same as setting it directly:

[code]

<StackPanel Grid.Row=”0” Margin=”24,16,0,12”>

</StackPanel>

[/code]

A Hierarchy of Resource Dictionaries

All visual elements have their own resource dictionary (e.g. a Resources property), not just a page. And the StaticResource mechanism doesn’t simply look in the current page’s resource dictionary for the matching key. It starts by checking the current element’s resource dictionary, and then it checks the parent element, its parent, and so on until it reaches the root element (the frame containing the current page). After that, it checks a resource dictionary defined on the Application object.

You can see the application-wide resource dictionary in any project, initially empty, in the Visual Studio-generated App.xaml file:

[code]

<Application …>
<!– Add a style to the application-wide resource dictionary –>
<Application.Resources>
</Application.Resources>

</Application>

[/code]

This means that you can define a toplevel set of resources and override them at arbitrary points in the tree of elements (similar to data contexts). Although each individual resource dictionary requires unique keys, the same key can be used in multiple dictionaries. The one “closest” to the element referencing the resource wins.

The reason why we’ve been able to use StaticResource to reference phone theme resources such as PhoneForegroundBrush and PhoneBackgroundBrush is that these resources are automatically injected into the app when it starts. You can see the resource dictionary for each combination of theme and accent color in ThemeResources.xaml files under %ProgramFiles%Microsoft SDKsWindows Phonev7.0Design.

If you reference a resource in C#, you need to retrieve it from the exact dictionary containing it, for example:

[code]

// Get a resource from the page’s resource dictionary
this.someElement.Margin = (Thickness)this.Resources[“PhoneTitlePanelMargin”];

[/code]

Or as done in the Ruler app:

[code]

// Get a resource from the application-wide resource dictionary
this.LayoutRoot.Background =
Application.Current.Resources[“PhoneChromeBrush”] as Brush;

[/code]

C# code doesn’t have an automatic mechanism to find a resource in multiple locations, like what happens in XAML.

Styles

A style is a pretty simple entity. It collects several property values that could otherwise be set individually. It does this with a collection of setters that each names a property and its value, as with the following style from Listing 9.1:

[code]

<!– A style for text blocks that act as a label for a text box –>
<Style x:Key=”LabelStyle” TargetType=”TextBlock”>
<Setter Property=”Foreground” Value=”{StaticResource PhoneSubtleBrush}”/>
<Setter Property=”Margin” Value=”24,17,24,-5”/>
</Style>

[/code]

Visual elements have a Style property that can be directly set to an instance of a style, but the point of using a style is to define it as a resource instead. That way, you get the flexibility of sharing and overriding styles. Styles can be applied just like any other resource:

[code]

<TextBlock Text=”Carrier name” Style=”{StaticResource LabelStyle}”/>

[/code]

Notice that styles stored as resources often contain their own references to other resources. A resource can reference earlier resources from the same resource dictionary, or from a parent resource dictionary (such as the application-wide resource dictionary containing PhoneSubtleBrush in this example).

Note that style setters are for properties only. You cannot attach event handlers as part of a style.

The Code-Behind

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

[code]

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Restore saved text box contents when entering this page
this.PhoneNumberTextBox.Text = Settings.PhoneNumber.Value;
this.CarrierTextBox.Text = Settings.Carrier.Value;
// Restore the time if it’s in the future, otherwise use the current time
if (Settings.CallTime.Value > DateTime.Now)
this.TimePicker.Value = Settings.CallTime.Value;
else
this.TimePicker.Value = DateTime.Now;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Persist contents when leaving this page for any reason
Settings.CallTime.Value = this.TimePicker.Value ?? DateTime.Now;
Settings.PhoneNumber.Value = this.PhoneNumberTextBox.Text;
Settings.Carrier.Value = this.CarrierTextBox.Text;
}
void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
// Select all text so it can be cleared with one keystroke
(sender as TextBox).SelectAll();
}
void TextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
// Cycle through the text boxes
if (sender == this.PhoneNumberTextBox)
this.CarrierTextBox.Focus();
else if (sender == this.CarrierTextBox)
this.TimePicker.Focus(); // Cycle back to the beginning
e.Handled = true;
}
}
void WaitForCallButton_Click(object sender, RoutedEventArgs e)
{
// Go to the next page
this.NavigationService.Navigate(new Uri(“/IncomingCallPage.xaml”,
UriKind.Relative));
}
void TimePicker_ValueChanged(object sender, DateTimeValueChangedEventArgs e)
{
// To prevent getting clobbered on way back in
Settings.CallTime.Value = e.NewDateTime ?? DateTime.Now;
}
}
}

[/code]

Notes:

  • Because the three settings used by this app need to be accessed on three different pages, they are defined as public fields on a separate class called Settings:[code]
    public static class Settings
    {
    public static readonly Setting<DateTime> CallTime =
    new Setting<DateTime>(“CallTime”, DateTime.Now);
    public static readonly Setting<string> PhoneNumber =
    new Setting<string>(“PhoneNumber”, “”);
    public static readonly Setting<string> Carrier =
    new Setting<string>(“Carrier”, “AT&T”);
    }
    [/code]

    This pattern is used throughout the rest of the book for settings that need to be shared across multiple pages.

  • The operation of the time picker causes a small wrinkle with our saving/restoring of settings in OnNavigatedFrom/OnNavigatedTo. Because changing the time picker’s value involves navigating to and from a different page, the following line in OnNavigatedTo wipes out the value that the user just chose, replacing it with the previously-saved value:[code] Settings.CallTime.Value = this.TimePicker.Value ?? DateTime.Now; [/code]

    To work around this, Listing 9.2 handles the time picker’s ValueChanged event (with the TimePicker_ValueChanged handler) and saves the new value instantly. This way, when OnNavigatedTo is called moments later, the assignment is a no-op.

  • The Value property on the time picker is a nullable DateTime, to support a state of no time being chosen. Unlike the date picker, however, a time picker’s initial value is null. Fake Call chooses to avoid handling this state by initializing it to DateTime.Now instead.

The Incoming-Call Page

The incoming-call page has two modes, shown in Figure 9.3 with ShowGridLines=”True” temporarily set on the page’s innermost grid. While waiting for the fake call to arrive, the screen is black to simulate it being off. When the call arrives, the screen mimics the incoming-call experience from the real Phone app. To help the user get used to how the app works, the “waiting mode” also shows a countdown to the call, but this can be hidden by tapping the screen. When using this app for real, the user should hide the countdown to avoid exposing the secret.

FIGURE 9.3 The two modes of the incoming-call page, with some grid lines showing to help explain the layout.
FIGURE 9.3 The two modes of the incoming-call page, with some grid lines showing to help explain the layout.

The User Interface

Listing 9.3 contains the XAML file for the incoming-call page.

LISTING 9.3 IncomingCall.xaml—The User Interface for Fake Call’s Incoming Call Page

[code]

<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.IncomingCallPage”
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=”Portrait”>
<!– Add one item to the page’s resource dictionary –>
<phone:PhoneApplicationPage.Resources>
<!– A style for the two text blocks in WaitingForCallPanel –>
<Style x:Key=”WaitingTextStyle” TargetType=”TextBlock”>
<Setter Property=”Foreground” Value=”#99FFFFFF”/>
<Setter Property=”FontFamily”
Value=”{StaticResource PhoneFontFamilySemiBold}”/>
<Setter Property=”FontSize” Value=”23”/>
</Style>
</phone:PhoneApplicationPage.Resources>
<!– A 1×1 grid holds two overlapping stack panels –>
<Grid>
<!– The initial panel that can show a countdown to the phone call –>
<StackPanel x:Name=”WaitingForCallPanel” Background=”Black”>
<TextBlock x:Name=”CountdownTextBlock” Margin=”11,120,0,0”
Style=”{StaticResource WaitingTextStyle}”/>
<TextBlock x:Name=”TapToHideTextBlock” Margin=”11,0,0,0”
Text=”(tap screen to hide)”
Style=”{StaticResource WaitingTextStyle}”/>
</StackPanel>
<!– The fake “incoming call” user interface –>
<StackPanel x:Name=”IncomingCallPanel” Visibility=”Collapsed”
Background=”{StaticResource PhoneChromeBrush}”>
<!– These two text blocks form the fake status bar –>
<TextBlock x:Name=”CurrentTimeTextBlock”
Style=”{StaticResource StatusBarTextStyle}”/>
<TextBlock x:Name=”CarrierTextBlock”
Style=”{StaticResource StatusBarTextStyle}”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<Grid Margin=”12,1,12,0” Height=”566”>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!– “INCOMING CALL” –>
<TextBlock Grid.ColumnSpan=”2” Text=”INCOMING CALL” Margin=”11,0,0,-10”
VerticalAlignment=”Bottom” FontSize=”23”
FontFamily=”{StaticResource PhoneFontFamilySemiBold}”
Foreground=”{StaticResource PhoneAccentBrush}”/>
<!– The phone number –>
<TextBlock x:Name=”PhoneNumberTextBlock” Grid.Row=”1” Grid.ColumnSpan=”2”
Margin=”6,22,0,51” TextWrapping=”Wrap” FontSize=”85”
LineHeight=”91” LineStackingStrategy=”BlockLineHeight”
FontFamily=”{StaticResource PhoneFontFamilySemiLight}” />
<!– The answer/ignore buttons –>
<Button Grid.Row=”2” Content=”answer” Click=”AnswerButton_Click”
local:Tilt.IsEnabled=”True”/>
<Button Grid.Column=”1” Grid.Row=”2” Content=”ignore”
Click=”IgnoreButton_Click” local:Tilt.IsEnabled=”True”/>
</Grid>
</StackPanel>
</Grid>
</phone:PhoneApplicationPage>

[/code]

Notes:

  • Unlike the main page, this page (and the next one) is portrait-only to match the behavior of the phone app.
  • This page (and the next one) uses a fake status bar. This enables it to blend in with the PhoneChromeBrush background. Although the real Phone app looks this way, third-party apps cannot change the color of the real status bar.
  • The WaitingTextStyle style is defined in this page’s resource dictionary, but the StatusBarTextStyle style is defined in the application-level resource dictionary inside App.xaml so it can be shared by two pages:[code]
    <Application …>
    <!– Add a style to the application-wide resource dictionary –>
    <Application.Resources>
    <Style x:Key=”StatusBarTextStyle” TargetType=”TextBlock”>
    <Setter Property=”HorizontalAlignment” Value=”Right”/>
    <Setter Property=”Margin” Value=”0,0,13,4”/>
    </Style>
    </Application.Resources>

    </Application>
    [/code]
  • PhoneNumberTextBlock wraps its text by setting its TextWrapping property to Wrap. This alone doesn’t exactly match the real Phone app, because the wrapped lines are too far apart. Therefore, it uses an explicit LineHeight setting to better match the tight wrapping, as shown in Figure 9.4.
FIGURE 9.4 The impact of giving a wrapping text block an explicit line height.
FIGURE 9.4 The impact of giving a wrapping text block an explicit line height.
  • The layout of this page keeps the answer and ignore buttons in a fixed position even as the length of the phone number text changes. Figure 9.5 shows the appearance of this page when the phone number is left blank, and when the user gets creative with long, custom text.
FIGURE 9.5 The text grows upward while the buttons remain in a fixed position.
FIGURE 9.5 The text grows upward while the buttons remain in a fixed position.

The Code-Behind

Listing 9.4 contains the code-behind for the incoming-call page.

LISTING 9.4 IncomingCall.xaml.cs—The Code-Behind for Fake Call’s Incoming Call Page

[code]

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Navigation;
using System.Windows.Threading;
using Microsoft.Devices;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace WindowsPhoneApp
{
public partial class IncomingCallPage : PhoneApplicationPage
{
// A timer that ticks once per second to keep track of the time
DispatcherTimer timer = new DispatcherTimer {
Interval = TimeSpan.FromSeconds(1)
};
// A variable-interval timer that performs the call vibration pattern
DispatcherTimer vibrationTimer = new DispatcherTimer();
// Bookkeeping for the vibration pattern used to simulate ringing
bool isRinging;
int vibrationStep;
public IncomingCallPage()
{
InitializeComponent();
this.timer.Tick += Timer_Tick;
this.vibrationTimer.Tick += VibrationTimer_Tick;
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
if (this.WaitingForCallPanel.Visibility == Visibility.Visible)
{
// Hide the contents of WaitingForCallPanel when the screen is tapped
this.CountdownTextBlock.Visibility = Visibility.Collapsed;
this.TapToHideTextBlock.Visibility = Visibility.Collapsed;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Respect the current settings
this.CarrierTextBlock.Text = Settings.Carrier.Value;
this.PhoneNumberTextBlock.Text = Settings.PhoneNumber.Value;
// Start the main timer
this.timer.Start();
Timer_Tick(null, null); // Force initial update
// While on this page, don’t allow the screen to auto-lock
PhoneApplicationService.Current.UserIdleDetectionMode =
IdleDetectionMode.Disabled;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Stop the main timer
this.timer.Stop();
// Stop ringing (if ringing)
StopRinging();
// Restore the ability for the screen to auto-lock when on other pages
PhoneApplicationService.Current.UserIdleDetectionMode =
IdleDetectionMode.Enabled;
}
void StartRinging()
{
// Show IncomingCallPanel and start the vibration pattern
isRinging = true;
this.WaitingForCallPanel.Visibility = Visibility.Collapsed;
this.IncomingCallPanel.Visibility = Visibility.Visible;
this.vibrationStep = 0;
this.vibrationTimer.Start();
}
void StopRinging()
{
// Hide IncomingCallPanel, stop the vibration, and set the next call time
// to two minutes from now
isRinging = false;
this.WaitingForCallPanel.Visibility = Visibility.Visible;
this.IncomingCallPanel.Visibility = Visibility.Collapsed;
this.vibrationTimer.Stop();
Settings.CallTime.Value = DateTime.Now + TimeSpan.FromMinutes(2);
Timer_Tick(null, null); // Force an update now
}
void Timer_Tick(object sender, EventArgs e)
{
// Show the current time on the fake status bar
this.CurrentTimeTextBlock.Text = DateTime.Now.ToString(“h:mm”);
TimeSpan delta = Settings.CallTime.Value – DateTime.Now;
if (delta > TimeSpan.Zero)
{
// It’s not time to ring yet. Update the countdown in case it is visible.
this.CountdownTextBlock.Text = “COUNTDOWN: “ + (int)delta.TotalHours + “:”
+ delta.Minutes.ToString(“00”) + “:”
+ Math.Ceiling(delta.Seconds).ToString(“00”);
}
else if (!this.isRinging)
{
// It’s time to ring
StartRinging();
}
}
void AnswerButton_Click(object sender, RoutedEventArgs e)
{
// Go to the next page
this.NavigationService.Navigate(new Uri(“/CallInProgressPage.xaml”,
UriKind.Relative));
}
void IgnoreButton_Click(object sender, RoutedEventArgs e)
{
StopRinging();
}
void VibrationTimer_Tick(object sender, EventArgs e)
{
// Make a short-long-short-long pattern of vibration
switch (this.vibrationStep % 4) // Cycle from 0 – 3
{
case 0:
case 2:
VibrateController.Default.Start(TimeSpan.FromSeconds(.1)); // short
// Leave space between this short and the next long
this.vibrationTimer.Interval = TimeSpan.FromSeconds(.4);
break;
case 1:
VibrateController.Default.Start(TimeSpan.FromSeconds(.4)); // long
// Leave more space between this long and the next short
this.vibrationTimer.Interval = TimeSpan.FromSeconds(.8);
break;
case 3:
VibrateController.Default.Start(TimeSpan.FromSeconds(.4)); // long
// Leave even more space after every other long
this.vibrationTimer.Interval = TimeSpan.FromSeconds(2.1);
break;
}
this.vibrationStep++;
// Stop ringing after 20 seconds (6 full cycles of the 4-part pattern)
if (this.vibrationStep == 24)
StopRinging();
}
}
}

[/code]

Notes:

  • The implementation of OnNavigatedTo and OnNavigatedFrom ensures that the app’s UserIdleDetectionMode property is set to Disabled while this page is active. As explained in the preceding chapter, this prevents the screen from automatically locking while the user waits for the fake phone call to arrive, as this would interfere with its operation. Unlike ApplicationIdleDetectionMode, UserIdleDetectionMode can be changed at any time. Running under the screen lock with ApplicationIdleDetectionMode is not useful for Fake Call, because even though it enables the phone to pretend-ring while it is locked, the user would still need to press the Power button, swipe upward, and enter their password (if the phone is password-protected) before seeing the incoming-call screen!
  • The processing of the carrier and phone number settings could have been done in the constructor rather than in OnNavigatedTo, but then the main page would have had to override its OnNavigatingFrom method and set these values there rather than OnNavigatedFrom to prevent this page from getting stale values. This page is constructed between the previous page’s OnNavigatingFrom and OnNavigatedFrom events.
  • Every time the phone stops ringing (either because the user tapped “ignore” or because they answered the fake call), the CallTime setting is changed to 2 minutes from the current time. This will simulate the person calling again every 2 minutes for as long as you keep the app open.
  • The time on the fake status bar is formatted with an “h:mm” string, ensuring that the hour and minutes are displayed without the corresponding AM or PM. This matches the phone’s real status bar except when it has the 24-hour clock setting turned on.
  • Pieces of the countdown string use the “00” format string to ensure that two digits are displayed, even if one is a padded 0.
  • The “ringing” is done with a pattern of vibration that mimics the sound of a real incoming call when the ringer is set to vibrate.

The Call-In-Progress Page

The final page—the call-in-progress page—is pictured in Figure 9.6 with ShowGridLines=”True” marked on its second grid.

 

FIGURE 9.6 The call-in-progress page, with some grid lines showing to help explain the layout.
FIGURE 9.6 The call-in-progress page, with some grid lines showing to help explain the layout.

 The User Interface

Listing 9.5 contains this page’s XAML file.

LISTING 9.5 CallInProgress.xaml—The User Interface for Fake Call’s Call-In-Progress Page

[code]

<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.CallInProgressPage”
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”
xmlns:sys=”clr-namespace:System;assembly=mscorlib”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”Portrait”>
<!– Add two items to the page’s resource dictionary –>
<phone:PhoneApplicationPage.Resources>
<Thickness x:Key=”ButtonMargin”>0,41,0,12</Thickness>
<sys:Int32 x:Key=”SmallButtonWidth”>88</sys:Int32>
</phone:PhoneApplicationPage.Resources>
<StackPanel Background=”{StaticResource PhoneChromeBrush}”
VerticalAlignment=”Top”>
<Grid>
<!– The fake signal-strength bars for the fake status bar –>
<Canvas>
<!– Add two styles to this canvas’s resource dictionary –>
<Canvas.Resources>
<Style x:Key=”BarOnStyle” TargetType=”Line”>
<Setter Property=”Stroke”
Value=”{StaticResource PhoneForegroundBrush}”/>
<Setter Property=”StrokeThickness” Value=”5”/>
<Setter Property=”Y2” Value=”26”/>
</Style>
<Style x:Key=”BarOffStyle” BasedOn=”{StaticResource BarOnStyle}”
TargetType=”Line”>
<Setter Property=”Stroke” Value=”{StaticResource PhoneSubtleBrush}”/>
<Setter Property=”Opacity” Value=”.3”/>
</Style>
</Canvas.Resources>
<Line Style=”{StaticResource BarOnStyle}”
X1=”16” X2=”16” Y1=”22”/>
<Line Style=”{StaticResource BarOnStyle}”
X1=”22” X2=”22” Y1=”18”/>
<Line Style=”{StaticResource BarOffStyle}”
X1=”28” X2=”28” Y1=”15”/>
<Line Style=”{StaticResource BarOffStyle}”
X1=”34” X2=”34” Y1=”11”/>
<Line Style=”{StaticResource BarOffStyle}”
X1=”40” X2=”40” Y1=”7”/>
</Canvas>
<!– The current time for the fake status bar–>
<TextBlock x:Name=”CurrentTimeTextBlock”
Style=”{StaticResource StatusBarTextStyle}”/>
</Grid>
<!– The carrier for the fake status bar–>
<TextBlock x:Name=”CarrierTextBlock”
Style=”{StaticResource StatusBarTextStyle}”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<Grid Margin=”12,1,12,0”>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width=”Auto”/>
<ColumnDefinition Width=”Auto”/>
</Grid.ColumnDefinitions>
<TextBlock x:Name=”CallDurationTextBlock” Grid.ColumnSpan=”3” FontSize=”21”
Margin=”12,20,0,0” Foreground=”{StaticResource PhoneAccentBrush}”/>
<TextBlock x:Name=”PhoneNumberTextBlock” Grid.Row=”1” Grid.ColumnSpan=”3”
Margin=”6,-5,0,28” TextWrapping=”Wrap” FontSize=”68”
FontFamily=”{StaticResource PhoneFontFamilySemiLight}” />
<!– The “end call” button –>
<Button Grid.Row=”2” Content=”end call” Click=”EndCallButton_Click”
Margin=”{StaticResource ButtonMargin}” local:Tilt.IsEnabled=”True”
Background=”{StaticResource PhoneAccentBrush}”/>
<!– The fake keypad button –>
<Button Grid.Row=”2” Grid.Column=”1” Margin=”{StaticResource ButtonMargin}”
Width=”{StaticResource SmallButtonWidth}” local:Tilt.IsEnabled=”True”>
<Canvas Width=”16” Height=”18”>
<!– Add a style to this canvas’s resource dictionary –>
<Canvas.Resources>
<Style x:Key=”SquareStyle” TargetType=”Rectangle”>
<Setter Property=”Width” Value=”4”/>
<Setter Property=”Height” Value=”4”/>
<Setter Property=”Fill”
Value=”{StaticResource PhoneForegroundBrush}”/>
</Style>
</Canvas.Resources>
<Rectangle Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”6” Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”12” Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Top=”6” Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”6” Canvas.Top=”6”
Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”12” Canvas.Top=”6”
Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Top=”12” Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”6” Canvas.Top=”12”
Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”12” Canvas.Top=”12”
Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”6” Canvas.Top=”18”
Style=”{StaticResource SquareStyle}”/>
</Canvas>
</Button>
<!– The fake double-arrow button –>
<Button Width=”{StaticResource SmallButtonWidth}” Grid.Row=”2”
Grid.Column=”2” Margin=”{StaticResource ButtonMargin}”
local:Tilt.IsEnabled=”True”>
<Path Fill=”{StaticResource PhoneForegroundBrush}”
Data=”M0,2 14,2 7,11z M0,13 14,13 7,22” />
</Button>
</Grid>
</StackPanel>
</phone:PhoneApplicationPage>

[/code]

Notes:

  • The SmallButtonWidth resource is an integer used as the width of the two small buttons. To create an integer element in XAML, the Int32 type is referenced from the System namespace in the mscorlib assembly.
  • This page has a second reason to use a fake status bar—so it can show fake signalstrength bars that mimic the real Phone app experience. The fake bars are created with 5 lines in a canvas (the first two always on and the remaining always off). To avoid repeating several property settings, two styles shared by the lines are placed in the canvas’s resource dictionary. There is no need to have the styles in the page’s resource dictionary because they are only used in this specific spot.
  • The keypad button contains ten rectangles to produce the appropriate appearance, and the doublearrow button is created with a Path shape.  These buttons don’t actually do anything; they are just there to visually match the real Phone app.
  • Unlike the incoming-call page, the content grows downward if the user has chosen long text that wraps, as shown in Figure 9.7.
FIGURE 9.7 The buttons get pushed downward to make room for long text.
FIGURE 9.7 The buttons get pushed downward to make room for long text.

The Code-Behind

Listing 9.6 contains the code-behind for this page.

LISTING 9.6 CallInProgress.xaml.cs—The Code-Behind for Fake Call’s Call-In-Progress Page

[code]

using System;
using System.Windows;
using System.Windows.Navigation;
using System.Windows.Threading;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace WindowsPhoneApp
{
public partial class CallInProgressPage : PhoneApplicationPage
{
// A timer that ticks once per second to keep track of the time
DispatcherTimer timer = new DispatcherTimer {
Interval = TimeSpan.FromSeconds(1) };
TimeSpan callDuration;
public CallInProgressPage()
{
InitializeComponent();
this.timer.Tick += Timer_Tick;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Respect the current settings
this.CarrierTextBlock.Text = Settings.Carrier.Value;
this.PhoneNumberTextBlock.Text = Settings.PhoneNumber.Value;
// Start at -1 seconds because Timer_Tick is about to increase it by 1
this.callDuration = TimeSpan.Zero – TimeSpan.FromSeconds(1);
// Start the main timer
this.timer.Start();
Timer_Tick(null, null); // Force an update now
// While on this page, don’t allow the screen to auto-lock
PhoneApplicationService.Current.UserIdleDetectionMode =
IdleDetectionMode.Disabled;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Stop the main timer
this.timer.Stop();
// Set the next call time to two minutes from now
Settings.CallTime.Value = DateTime.Now + TimeSpan.FromMinutes(2);
// Restore the ability for the screen to auto-lock when on other pages
PhoneApplicationService.Current.UserIdleDetectionMode =
IdleDetectionMode.Enabled;
}
void Timer_Tick(object sender, EventArgs e)
{
// Show the current time on the fake status bar
this.CurrentTimeTextBlock.Text = DateTime.Now.ToString(“h:mm”);
// Update the call duration display
this.callDuration = this.callDuration.Add(TimeSpan.FromSeconds(1));
this.CallDurationTextBlock.Text = (int)this.callDuration.TotalMinutes +
“:” + this.callDuration.Seconds.ToString(“00”);
}
void EndCallButton_Click(object sender, RoutedEventArgs e)
{
// Go back to the incoming call page, which will wait for the next call
if (this.NavigationService.CanGoBack)
this.NavigationService.GoBack();
}
}
}

[/code]

The code-behind for this page is similar to the code-behind for the previous page, just a bit simpler. Ideally, the app would just exit when the user taps the “end call” button, but there’s no good way to accomplish this. Therefore, after the user taps “end call,” they can either press the Power button to lock their phone or the Start button to leave this app—if they don’t want to be fake-called again in 2 minutes.

Exiting an App Programmatically

To make the app easier to quickly exit, you could merge all of Fake Call’s functionality onto one page to avoid filling the back stack. However, this would make the code messier, and the user would still need to press the hardware Back button to exit the app.

Throwing an unhandled exception would force the app to exit, but this has two problems. If you do this, settings won’t be properly persisted to disk (because this is internally done during a graceful app shutdown). Also, such behavior would not get past the marketplace certification process. So don’t ever attempt to force an app to exit this way!

Another approach would be to call XNA’s Game.Exit method from the Microsoft.Xna. Framework.Game assembly.However, calling anything from this assembly will cause your app to fail marketplace certification.You might be able to get away with using .NET reflection to call Game.Exit in a way that the certification process doesn’t detect, but I wouldn’t recommend it.

The Finished Product

Fake Call (Resources & Styles)

Date Diff

Date Diff, named after the T-SQL DATEDIFF function, tells you how many days or weeks apart two dates are. This app requires very little code, thanks to the Silverlight for Windows Phone Toolkit.

The Silverlight for Windows Phone Toolkit, available at http://silverlight.codeplex.com, contains lots of rich controls and functionality leveraged throughout this book. It’s open source, so you can even tweak it relatively easily! This app makes use of its date picker control, which matches the date picker used by the phone’s built-in apps. It starts out looking like a text box, but it presents a rich full-page interface when tapped.

Future releases of the Silverlight for Windows Phone Toolkit may not be compatible with earlier releases!

The goal of the toolkit, besides providing developers with handy functionality, is to rapidly iterate on features that might become a part of the official Windows Phone Developer Tools in the future.Therefore, the team is not shy about making changes that can break code compiled against a previous version. Also, when features from the toolkit are added to the Windows Phone Developer Tools, they will likely be removed from the toolkit.

As long as you continue to use the same version of the toolkit in your apps, future changes should not cause you any trouble.The only thing to be careful about is upgrading the toolkit and recompiling your apps without thoroughly testing them.The apps in this book use the February 2011 release of the toolkit.

The Main Page

Date Diff’s only page (other than the page automatically presented by the date picker) is pictured in Figure 7.1. The user can customize the date in each date picker instance and then see the difference below.

FIGURE 7.1 The main page enables the selection of two dates and then shows the difference.
FIGURE 7.1 The main page enables the selection of two dates and then shows the difference.

LISTING 7.1 MainPage.xaml—The User Interface for Date Diff’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”
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 header, with some tweaks –>
<StackPanel Margin=”24,16,0,12”>
<TextBlock Text=”DATE DIFF” Margin=”-1,0,0,0”
FontFamily=”{StaticResource PhoneFontFamilySemiBold}”
FontSize=”{StaticResource PhoneFontSizeMedium}”/>
<TextBlock Text=”pick two dates” Margin=”-3,-10,0,0”
FontFamily=”{StaticResource PhoneFontFamilySemiLight}”
FontSize=”{StaticResource PhoneFontSizeExtraExtraLarge}”/>
</StackPanel>
<StackPanel Grid.Row=”1”>
<!– Date #1 –>
<TextBlock Text=”Date #1” Margin=”24,17,24,-5”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<toolkit:DatePicker x:Name=”DatePicker1” Margin=”12,0”
local:Tilt.IsEnabled=”True” ValueChanged=”DatePicker_ValueChanged”/>
<!– Date #2 –>
<TextBlock Text=”Date #2” Margin=”24,17,24,-5”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<toolkit:DatePicker x:Name=”DatePicker2” Margin=”12,0”
local:Tilt.IsEnabled=”True” ValueChanged=”DatePicker_ValueChanged”/>
<!– The result –>
<TextBlock x:Name=”ResultTextBlock” Margin=”24,0”
FontSize=”{StaticResource PhoneFontSizeExtraLarge}”/>
</StackPanel>
</Grid>
</phone:PhoneApplicationPage>

[/code]

Notes:

  • Although DatePicker is in the same Microsoft.Phone.Controls namespace as other controls, it resides in the Microsoft.Phone.Controls.Toolkit assembly, so this XAML file requires a separate XML namespace declaration. By convention, apps in this book use the toolkit prefix for this namespace/assembly.
  • The two date pickers automatically show today’s date by default, and when tapped they invoke the full-screen picker interface shown at the beginning of this chapter. The tilt effect applied to each date picker only affects the inline display; not the fullscreen picker.

The date picker requires two specific images to be included in your project!

The page shown by the date picker uses an application bar with the standard “done” and “cancel” buttons.However, you are responsible for including the two icon images in your project (and marking them with a Build Action of Content) with the proper names and location. If you fail to do this, you’ll see the standard error icons in their place.

The two icons must be named ApplicationBar.Check.png and ApplicationBar.Cancel.png, and they must be placed in a folder called Toolkit.Content in the root of your project.These image files are included with the Silverlight for Windows Phone Toolkit

This requirement is caused by the application bar’s limitation of only working with images marked as content. Listing 7.2 contains the

LISTING 7.2 MainPage.xaml.cs—The Code-Behind for Date Diff’s Main Page

[code]

using System;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
}
void DatePicker_ValueChanged(object sender, DateTimeValueChangedEventArgs e)
{
// Do the calculation
TimeSpan span = this.DatePicker2.Value.Value – this.DatePicker1.Value.Value;
// Format the result
int days = span.Days;
int wholeWeeks = days / 7;
int remainder = days % 7;
string result = days + (Math.Abs(days) == 1 ? “ day” : “ days”);
if (Math.Abs(days) > 14)
{
result += “n(“ + wholeWeeks + “ weeks”;
if (remainder != 0)
result += “, “ + remainder
+ (Math.Abs(remainder) == 1 ? “ day” : “ days”);
result += “)”;
}
// Display the result
this.ResultTextBlock.Text = result;
}
}
}

[/code]

Notes:

  • Each date picker’s date can be retrieved via its Value property, which is a nullable DateTime that has its own Value property. The nullability is handy because it enables each picker to express the concept of no date being selected. However, it also results in awkward-looking Value.Value property accesses. Because Value can only become null programmatically, this code can safely access each Value subproperty without checking for null.
  • Most of the code is concerned with formatting the string, which takes a few different forms depending on how far apart the two dates are.

The Finished Product

DATE DIFF

Media

Windows Phone 7 physical devices can store and display photos, play video and audio files, and have a built-in FM radio. To display photos and images, you can use the standard Silverlight controls such as the Image control. You can include the image file in your project and set the Build Action property to Content and the Copy to Output Directory property to Copy if newer or Copy Always, so that it is included in the XAP file that uploads to the device. You then specify the image as the source for the control. Alternatively, you can specify a remote URL for the image, and it will be downloaded and displayed
just as in a web browser. Images must be in JPEG (.jpg) or PNG (.png) format.

To display a video or play an audio file, you can use the Silverlight MediaElement control. You can include the video or audio file in your project and set the Build Action property to Content and the Copy to Output Directory property to Copy if newer or Copy Always, and then specify it as the source of the control.

XML
<MediaElement Source=”MyVideo.wmv” Width=”300″ Height=”300″
AutoPlay=”True”/>

Alternatively, you can specify a remote URL for the video or audio file, and it will be streamed or downloaded and played just as in a Web browser. For a list of the supported video and audio formats, see “Supported Media Codecs for Windows Phone” on MSDN (http://msdn.microsoft.com/en-us/library/ff462087(VS.92).aspx). For information about using the MediaElement control, see “MediaElement” on MSDN (http://msdn.microsoft.com/en-us/library/bb980132(VS.95).aspx).

An alternative approach for video and audio files is to use the MediaPlayerLauncher task. You create an instance of this class, set the required properties, and the call the Show method. In this example, the Build Action property of the video file is set to Content and the Copy to Output Directory property is set to Copy if newer so that the video file is included in the XAP file uploaded to the device.

C#
MediaPlayerLauncher mediaLauncher = new MediaPlayerLauncher();
mediaLauncher.Controls = MediaPlaybackControls.All;
mediaLauncher.Location = MediaLocationType.Install;
mediaLauncher.Media = new Uri(“MyVideo.wmv”, UriKind.Relative);
mediaLauncher.Show();

Alternatively, you can copy the media item to isolated storage on the device and specify the MediaLocation property as Media LocationType.Data. For more information, see “MediaPlayerLauncher Class” on MSDN (http://msdn.microsoft.com/en-us/library/microsoft.phone.tasks.mediaplayerlauncher(VS.92).aspx).

Note: When using the MediaPlayerLauncher task in the emulator, you may find that video frames render only if you repeatedly click on the UI. Each click advances the video by one frame and then the display turns black.

Selecting a Photo on the Device

You can select an existing photo from the user’s media collection using the PhotoChooserTask class. You specify a callback handler to execute when the user selects a photo, and optionally the size for the picture that will be returned.

C#
PhotoChooserTask photoTask = new PhotoChooserTask();
photoTask.Completed += new EventHandler<PhotoResult>
(PhotoTask_Completed);
photoTask.PixelHeight = 500;
photoTask.PixelWidth = 500;
photoTask.Show();

When the user selects a picture, the callback can obtain the name of the picture and the Stream instance pointing to it. You must decode the byte stream into an appropriate image format for display, such as in an Image control on the page, as shown here. To use this code, you must add a reference to the namespaces System.Windows. Media.Imaging and Microsoft.Phone (for the PictureDecoder class).

C#
void PhotoTask_Completed(object sender, PhotoResult result)
{
if (result.Error == null && result.TaskResult == TaskResult.OK)
{
string fileName = result.OriginalFileName;
WriteableBitmap thePhoto = PictureDecoder.DecodeJpeg(
result.ChosenPhoto);
MyImageControl.Source = thePhoto;
}
}

For more information about using media such as photos, video, and audio on Windows Phone 7, see “Media for Windows Phone” (http://msdn.microsoft.com/en-us/library/ff402560(VS.92).aspx) and “Photos for Windows Phone” (http://msdn.microsoft.com/en-us/library/ff402553(VS.92).aspx) on MSDN.