Baby Sign Language

These days, it’s quite common for parents to teach their babies a limited amount of sign language before they are old enough to talk. This ability to communicate simple wishes like “eat” or “milk” at an early age can reduce their frustration and, according to some experts, even accelerate their language skills. Of course, to teach a baby sign language, the parent must learn some! That’s where this app comes in.

The Baby Sign Language app explains how to do eight common signs that are helpful for a baby to learn by showing an illustration and written instructions for each one. The signs are organized in a two-level list—the first level contains three categories, and the second level contains the subset of signs belonging to the chosen category.

This project is similar to the auto-generated project you would get by creating a “Windows Phone Databound Application” project in Visual Studio. Both feature a main page with a list box, and a details page for displaying details about the selected item from the list. Both also leverage data binding.

Page Navigation

Although Visual Studio projects are given a single page by default, you can add more pages by right-clicking the project in Solution Explorer and then selecting Add, New Item…, Windows Phone Landscape Page or Windows Phone Portrait Page. (The two choices simply set the SupportedOrientations and Orientation properties differently on the generated pages. You can choose either option and easily switch the property values later to make it behave however you want.) The generated page looks just like the initial contents of MainPage.xaml but with whatever name you chose in the Add New Item dialog. If you add a new page called DetailsPage, two files get generated to define the DetailsPage class: DetailsPage.xaml and DetailsPage.xaml.cs.

The Baby Sign Language app uses three different pages: the main page, a details page, and an about page. Figure 6.1 maps out the possible paths of forward navigation through these pages. On any page, pressing the hardware Back button goes to the previous page (or exits the app when you’re on the initial page).

FIGURE 6.1 The navigation structure of the multi-page Baby Sign Language app.
FIGURE 6.1 The navigation structure of the multi-page Baby Sign Language app.

Notice that the main page is used for both levels of the categorized list! When an instance of MainPage is the first page of the app, it shows the three categories of signs. When you tap any of the three categories, the app navigates to a new instance of MainPage that instead shows the list of signs in the chosen category. From one of these instances, tapping a sign navigates to DetailsPage to explain the specific sign.

Every instance of MainPage has a link to an about page in its application bar menu. Pressing the hardware Back button from the about page goes back to whichever instance brought you there.

This section describes how to implement this or any other page navigation scheme. It examines

  • Pages, their frame, and their navigation service
  • Navigating forward and back
  • Passing data between pages
  • Designing a proper navigation scheme

Pages,Their Frame, and Their Navigation Service

Although it’s natural to think of a page as the root element of an app (especially for single-page apps), all pages actually are contained in something called a frame. (The frames discussed in this book are technically instances of a class called
PhoneApplicationFrame, just as pages are instances of a class called PhoneApplicationPage.)

The frame provides several members to enable page-to-page navigation, most importantly a method called Navigate. It also defines Navigating and Navigated events, among others. You rarely need to interact with the frame directly, however, because its navigation methods, properties, and events are exposed via a property on every page called NavigationService (of type NavigationService). Furthermore, pages already have OnNavigatedTo and OnNavigatedFrom methods (seen in the previous chapters) that are called in response to the relevant Navigated event. Using these methods and the NavigationService property is preferable over interacting with the frame directly, as it makes your code less intertwined.

Frame Interaction inside App.xaml.cs

Inside the App.xaml.cs code-behind file (which we haven’t needed to touch for the apps so far), you can find code that interacts with the app’s frame.The App class (derived from System.Windows.Application) is given a RootFrame property to expose the frame to the rest of the app:

[code]

// Easy access to the root frame
public PhoneApplicationFrame RootFrame { get; private set; }

[/code]

However, this is not really necessary because the frame is also set as the base Application class’s root visual later in the file.Therefore, code anywhere in your app has two ways to access the frame:

[code]

PhoneApplicationFrame frame = (Application.Current as App).RootFrame;

[/code]

or

[code]

PhoneApplicationFrame frame =
Application.Current.RootVisual as PhoneApplicationFrame;

[/code]

Navigation occurs asynchronously, so handling some navigation events can be important. If you attempt navigate to a bogus page, the attempt fails with no explanation unless you handle the NavigationFailed event. Fortunately, this is already done inside App.xaml.cs,with an implementation that halts the debugger (but does nothing when the app runs normally) to give you, the developer, a chance to see what went wrong:

[code]

// Code to execute if a navigation fails
void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
if (System.Diagnostics.Debugger.IsAttached)
{
// A navigation has failed; break into the debugger
System.Diagnostics.Debugger.Break();
}
}

[/code]

This file has a similar handler for unhandled exceptions in your app.

 

Navigating Forward and Back

To navigate from one page to another, you call the navigation service’s Navigate method with a URI pointing to the destination page. For example, tapping an “instructions” button on an application bar can navigate to an instructions page if it has the following event handler attached to its Click event:

[code]

void InstructionsButton_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/InstructionsPage.xaml”,
UriKind.Relative));
}

[/code]

The URI refers to the XAML file compiled into your project, and its path represents the folder path in your source
code. If InstructionsPage.xaml were placed in a PagesInstructions subfolder, the URI should be “/Pages/Instructions/InstructionsPage.xaml”.

Just like a web browser, the navigation service maintains a back stack and a forward stack. In addition to the Navigate method, the navigation service exposes GoBack and GoForward methods. Table 6.1 explains the behavior of these three methods and their impact on the back and forward stacks.

Navigation Effects on the Back and Forward Stacks55 12-33-41

GoBack fails when the back stack is empty (which means you’re currently on the app’s initial page), and GoForward fails when the forward stack is empty. If a piece of code is not certain what the states of these stacks are, it can check the Boolean CanGoBack and CanGoForward properties first.

Although the GoBack and GoForward methods enable you to put back and forward buttons in your user interface, do not do this! With small exceptions explained in the warning on the next page, Windows Phone and its apps do not put such buttons or links on the screen. Going back is handled with the hardware Back button, and going forward to a page on the forward stack is not usually done. (The user can start a new forward navigation to the same destination by tapping whatever button or link caused the original forward navigation.) Consider that even the Internet Explorer app does not have a back button on the screen!

Not only is adding such buttons against design guidelines, it also is extra clutter for users who understand the navigation model of the phone.

Passing Data Between Pages

Often, when you navigate to a page, you need to communicate some information to it. In the Baby Sign Language app, when you tap on an item in the list, the next page needs to know what item you just tapped so it can display the appropriate data. Other apps have times when the user must navigate to a new page to select something or fill out a form,
and that data needs to be communicated back to the original page when the new page is dismissed.

Passing data forward can be handled the same way as on the Web—by passing parameters in a query string. A query string is simply a ? appended to the URI string followed by one or more name/value pairs in a format such as name1=value1&name2=value2.

The Baby Sign Language app leverages such query parameters in several places. For example, it uses a generic about page shared by many of the apps in this book. This page customizes its display for whatever app is using it by enabling the app’s name to be passed in the query string as an appName parameter:

[code]

this.NavigationService.Navigate(new Uri(
“/Shared/About/AboutPage.xaml?appName=Baby Sign Language”,
UriKind.Relative));

[/code]

When we examine the rest of this app’s code, you’ll see how the destination page is able to retrieve the query string so it can act on such name/value pairs.

Designing a Proper Navigation Scheme

Figuring out the best way to organize your app into separate pages and figuring out the best way for pages to link to
each other isn’t always easy; it can take practice. Sometimes the most straightforward structure causes unwanted effects,
especially when it comes to managing the back stack.

Due to the asynchronous nature of navigation, you cannot call GoBack or GoForward (or Navigate) repeatedly in a loop. Invoking a new navigation action while navigation is already in progress causes an InvalidOperationException to be thrown. Instead, you could navigate forward or backward multiple times in a row by having a listener to the frame’s Navigated event continually make the next GoBack or GoForward call once the in-progress navigation has completed. Besides being messy, however, this can produce unwanted delays and visual effects.

Because you can’t programmatically exit the app from any page, or even go back more than one page at a time very easily, care must be taken to not to make the back stack grow too large. Your users will likely get annoyed if they have to go back through several pages in order to exit your app.

Figure 6.3 demonstrates a hypothetical page structure and navigation flow for one of the games created in Volume II of this book series. The game has several logical screens, including a main menu (seen when launching the app), the game itself (seen after tapping “play”), and a scoreboard. It’s natural to make each logical screen be a distinct page. However, if your desire is to automatically navigate to the scoreboard when the game ends (perhaps to record a new high score), this causes a problem: What should happen when the user presses the hardware Back button while on the scoreboard page?
Ideally, the user would return to the main menu, but the game page is still on the back stack!

FIGURE 6.3 A common problem with a navigation scheme for a game.
FIGURE 6.3 A common problem with a navigation scheme for a game.

We know that instantly navigating back two pages is not an option. After the hardware Back button takes the user from the scoreboard page to the game page, the game page could detect this situation and call GoBack to trigger a new  navigation to the main menu, but this has the same problems of delays and unwanted visual effects mentioned earlier. (It’s effectively the same technique as using the frame’s Navigated event.)

The solution chosen for such games in Volume II is to merge the main menu and game page into two different modes of the same page. Navigating to and from the scoreboard page then works as desired, as long as this combined page knows when it should show the menu and when it should be actively running the game. (This structure happens to be more convenient for additional reasons, such as the behavior of pausing and unpausing the game.)

The Main Page

Now that we’ve covered page navigation in depth, it’s time to see how the Baby Sign Language app is implemented. Starting with MainPage, we’ll look at its user interface, the definition of the data used by the app, and its code-behind. You’ll also learn about a powerful Silverlight feature known as data templates.

The User Interface

Listing 6.1 contains the XAML for MainPage, the page with the list of items. As indicated in Figure 6.1, this page can appear with four different sets of data: the list of categories, and the lists of signs for each of the three categories.

LISTING 6.1 MainPage.xaml—The Primary User Interface for Baby Sign Language

[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”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”PortraitOrLandscape”
shell:SystemTray.IsVisible=”True”>
<!– An application bar with an “about” menu item and no buttons –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text=”about” Click=”AboutMenuItem_Click”/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<Grid x:Name=”LayoutRoot”>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard header, with some tweaks –>
<StackPanel Margin=”24,16,0,12”>
<TextBlock Text=”BABY SIGN LANGUAGE” Margin=”-1,0,0,0”
FontFamily=”{StaticResource PhoneFontFamilySemiBold}”
FontSize=”{StaticResource PhoneFontSizeMedium}”/>
<TextBlock x:Name=”PageTitle” Text=”categories” Margin=”-3,-10,0,0”
FontFamily=”{StaticResource PhoneFontFamilySemiLight}”
FontSize=”{StaticResource PhoneFontSizeExtraExtraLarge}”/>
</StackPanel>
<Grid Grid.Row=”1” Margin=”{StaticResource PhoneHorizontalMargin}”>
<ListBox x:Name=”ListBox” SelectionChanged=”ListBox_SelectionChanged”>
<ListBox.ItemTemplate>
<!– The data template controls how each item renders –>
<DataTemplate>
<!– The text block displays value of the
current item’s Name property –>
<TextBlock Text=”{Binding Name}”
Margin=”{StaticResource PhoneMargin}”
Style=”{StaticResource PhoneTextExtraLargeStyle}”
local:Tilt.IsEnabled=”True”/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</phone:PhoneApplicationPage>

[/code]

Notes:

  • This page has a button-less application bar with just a single menu item for navigating to the about page. Viewing an about page is not an important-enough action to warrant a button.
  • Although the page title is initialized to “categories,” the code-behind updates this text when this page is used to display the other three lists.
  • A list box is a lot like a stack panel in a scroll viewer, but it supports additional behaviors related to item selection and data binding.
  • The list box is empty (to be filled by the code-behind), but its ItemTemplate property is set to an instance of a data template that gives each item in the list a specific appearance. Data templates are examined in the upcoming “Data Templates” section.
  • The list box has a SelectionChanged event that is raised when a new item is tapped (or selected programmatically). A handler is attached to this, which we’ll examine in the code-behind.

The Data

Before looking at the code-behind for MainPage, we should see how the categorized data is represented in code. This project defines a Category class that contains a name and a list of signs that belong to the category:

[code]

public class Category
{
public string Name { get; private set; }
public IList<Sign> Signs { get; private set; }
public Category(string name, IList<Sign> signs)
{
this.Name = name;
this.Signs = signs;
}
}
Sign is defined as follows, storing a name, a path to an image, and a description:
public class Sign
{
public string Name { get; private set; }
public Uri ImageUri { get; private set; }
public string Description { get; private set; }
public Sign(string name, Uri imageUri, string description)
{
this.Name = name;
this.ImageUri = imageUri;
this.Description = description;
}
}

[/code]

The actual data (three categories and eight signs) is represented in a static Data.Categories array that defines everything inline, leveraging the constructors for Category and Sign:

[code]

public class Data
{
public static readonly Category[] Categories = {
new Category(“eating & drinking”,
new Sign[] {
new Sign(“eat”, new Uri(“Images/eat.png”, UriKind.Relative), “…”),
new Sign(“milk”, new Uri(“Images/milk.png”, UriKind.Relative), “…”),
new Sign(“water”, new Uri(“Images/water.png”, UriKind.Relative), “…”)
}),
new Category(“people”,
new Sign[] {
new Sign(“daddy”, new Uri(“Images/daddy.png”, UriKind.Relative), “…”),
new Sign(“mommy”, new Uri(“Images/mommy.png”, UriKind.Relative), “…”)
}),
new Category(“other”,
new Sign[] {
new Sign(“all done”, new Uri(“Images/alldone.png”,
UriKind.Relative), “…”),
new Sign(“more”, new Uri(“Images/more.png”, UriKind.Relative), “…”),
new Sign(“play”, new Uri(“Images/play.png”, UriKind.Relative), “…”)
})
};
}

[/code]

Therefore, Data.Categories is a three-element array, and each of its elements has a Signs property that is a two- or three-element array.

There are many alternative approaches for representing this data, such as defining it in a file that is read when the app is initialized and dynamically constructing all the Category and Sign instances corresponding to the file contents. Part III, “Storing & Retrieving Local Data,” discusses such options. However, defining the data in a C# file, as done here, works just fine.

The Code-Behind

Listing 6.2 contains the code-behind for MainPage, which is responsible for determining which of the four lists it should be showing and then showing the correct data.

LISTING 6.2 MainPage.xaml.cs—The Main Code-Behind for Baby Sign Language

[code]

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
int categoryIndex = -1;
public MainPage()
{
InitializeComponent();
this.Loaded += MainPage_Loaded;
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (this.ListBox.Items.Count > 0)
return; // We already added the data.
// (We must be navigating back to this page.)
if (this.categoryIndex == -1)
{
// This is the root page. Fill the list box with the categories.
foreach (Category category in Data.Categories)
this.ListBox.Items.Add(category);
}
else
{
// This is a page for a specific category.
// Fill the list box with the category’s items.
foreach (Sign sign in Data.Categories[this.categoryIndex].Signs)
this.ListBox.Items.Add(sign);
}
}
void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (this.ListBox.SelectedIndex == -1)
return; // The selection was cleared, so do nothing
if (this.categoryIndex == -1)
{
// This is the root page, so the selection is a category. Navigate to a
// new instance of this page initialized with the chosen category.
this.NavigationService.Navigate(new Uri(“/MainPage.xaml?categoryIndex=” +
this.ListBox.SelectedIndex, UriKind.Relative));
}
else
{
// We’re already on the page for a specific category, so the selection is
// a sign. Navigate to the details page for this sign.
this.NavigationService.Navigate(new Uri(
“/DetailsPage.xaml?categoryIndex=” + this.categoryIndex +
“&signIndex=” + this.ListBox.SelectedIndex, UriKind.Relative));
}
// Clear the selection so the same item can be selected
// again on subsequent visits to this page
this.ListBox.SelectedIndex = -1;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (this.NavigationContext.QueryString.ContainsKey(“categoryIndex”))
{
// This is the page for a specific category.
// Remember its index and display its name.
this.categoryIndex = int.Parse(
this.NavigationContext.QueryString[“categoryIndex”]);
this.PageTitle.Text = Data.Categories[this.categoryIndex].Name;
}
}
void AboutMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(
“/Shared/About/AboutPage.xaml?appName=Baby Sign Language”,
UriKind.Relative));
}
}
}

[/code]

Notes:

  • This page uses a categoryIndex member to determine which list to show. A value of -1 means no category has been selected, so this is the initial page that must show the categories. Any other value means that a category has been chosen, and that value represents the chosen category’s index in the Data.Categories list (0, 1, or 2).
  • Inside the page’s Loaded event handler (MainPage_Loaded), the list box’s items are filled based on the value of categoryIndex. Notice that whereas panels have a Children collection, list box’s collection is called Items. The index of each item in the list box’s Items collection ends up matching the index in the original list, a fact that is leveraged in the rest of the app.
  • Inside the list box’s SelectionChanged event handler (ListBox_SelectionChanged), a navigation is performed in response to one of the items in the list being tapped. Which page it navigates to, and the data passed to it, depends on which item was tapped and the current value of categoryIndex. If categoryIndex is -1, the code navigates to a new instance of MainPage but passes along a new categoryIndex in the query string that indicates which category was just selected. (Without passing this value, this would navigate to an identical-looking instance and the user would be trapped in a loop.) List boxes have a SelectedIndex property that keeps track of the selected item’s index, so we can simply use this as the value of categoryIndex for the new MainPage instance. If categoryIndex is already nonnegative, the code navigates to DetailsPage instead and passes along two pieces of data: the existing categoryIndex, and the index of the selected sign inside that category. The latter is again available to us from the list box’s SelectedIndex property.
  • At the end of ListBox_ SelectionChanged, SelectedIndex is reset to -1 (meaning no item is selected). This is important to ensure that the page acts as expected when the user navigates back to it. A list box only raises its SelectionChanged event when the selected item changes, meaning that tapping on the same item multiple times in a row has no effect after the first time.
  • The implementation of OnNavigatedTo reveals how the categoryIndex member gets set to the value of the categoryIndex from the query string. Nothing is automatic; the code must explicitly check for any query parameters it expects. The query string can be accessed from any page’s NavigationContext property. NavigationContext’s QueryString property exposes the name/value pairs via a convenient dictionary, so you don’t have to parse the string to get the individual values. Each value is still a string, however, so this code uses integer’s Parse method to turn the retrieved categoryIndex value into an integer. This code also updates the page’s title by fetching the name of the current category.

Data Templates

In Listing 6.2, notice that Category and Sign objects were added directly to the list box’s Items collection. This is a new concept. In the Stopwatch app, we explicitly created new visual elements (a grid, a text block, and so on) every time we needed to add a new item to the stack panel. Panels require that its children are classes that derive from UIElement (visual objects that have their own rendering behavior), but list boxes enable us to add arbitrary data objects to its contents.

But what does it mean visually to add Category and Sign objects to a list box? Figure 6.4 shows what the initial instance of MainPage would look like if the list box’s ItemTemplate property setting was removed from the XAML in Listing 6.1, as well as what the next instance would look like after tapping the first category.

FIGURE 6.4 The appearance of two MainPage instances when no data template is applied.
FIGURE 6.4 The appearance of two MainPage instances when no data template is applied.

By default, each item is rendered as a plain text block whose text is whatever gets returned by each object’s ToString method (and by default, this is the namespacequalified class name). In this state, the app still “works”—you can tap items to get to the proper details page (which renders completely correctly). Notice that the second page instance in Figure 6.4 still has the correct title, because that is fetched directly from the data source based on its index, regardless of how the items in the list box actually render.

Of course, this rendering is not satisfactory. You could override the ToString method on Category and Sign and have them return the value of their Name property, but this is still pretty limiting. For example, the default margin and font size seen in Figure 6.4 is not satisfactory, and the items do not tilt when you press them. And what if you wanted to customize the rendering even further?

That’s where data templates come in. A data template is a visual element that can be applied to nonvisual objects. (And this visual element could be a panel containing a complex tree of elements.) When used as the value for a list box’s ItemTemplate property, the template is used for each item in the list.

Figure 6.5 shows what the initial page of this app would look like if you replaced the data template in Listing 6.1 with the following one:

FIGURE 6.5 The appearance of the initial MainPage instance when a silly data template is applied.
FIGURE 6.5 The appearance of the initial MainPage instance when a silly data template is applied.

[code]

<ListBox …>
<ListBox.ItemTemplate>
<!– The data template controls how each item renders –>
<DataTemplate>
<StackPanel>
<TextBlock Text=”one” Foreground=”Red”/>
<TextBlock Text=”two” Foreground=”Aqua”/>
<TextBlock Text=”three” Foreground=”Magenta”/>
<Button Content=”four”/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

[/code]

Besides being a silly data template, each visual item has nothing to do with the data item it’s supposed to represent. Each item looks identical!

A proper data template needs a way to access the data object so it can customize its display appropriately. This can be done with special data binding syntax of the form {Binding XXX}, where XXX is a property of the source data object.

In Listing 6.1, the data template successfully used by this app’s list box is

[code]

<DataTemplate>
<!– The text block displays value of the
current item’s Name property –>
<TextBlock Text=”{Binding Name}”
Margin=”{StaticResource PhoneMargin}”
Style=”{StaticResource PhoneTextExtraLargeStyle}”
local:Tilt.IsEnabled=”True”/>
</DataTemplate>

[/code]

It’s just a text block whose text is automatically set to the value of each item’s Name property. The neat thing about this is that the same data template works for both Category and Sign objects because they both happen to have a property called Name. If you were to change the value to {Binding Description} instead, it would show the description of each Sign, but the text block would be empty for each Category because it doesn’t have a Description property.

The Details Page

The details page used by Baby Sign Language contains the information that the user is looking for at the end of the navigation chain. It displays the three properties of the chosen Sign object: its name, image, and description. Listing 6.3 contains the XAML for this page.

LISTING 6.3 DetailsPage.xaml—The User Interface for the Baby Sign Language Details Page

[code]

<phone:PhoneApplicationPage x:Class=”WindowsPhoneApp.DetailsPage”
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”
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=”BABY SIGN LANGUAGE” Margin=”-1,0,0,0”
FontFamily=”{StaticResource PhoneFontFamilySemiBold}”
FontSize=”{StaticResource PhoneFontSizeMedium}”/>
<TextBlock x:Name=”PageTitle” Text=”” Margin=”-3,-10,0,0”
FontFamily=”{StaticResource PhoneFontFamilySemiLight}”
FontSize=”{StaticResource PhoneFontSizeExtraExtraLarge}”/>
</StackPanel>
<ScrollViewer Grid.Row=”1”>
<StackPanel Margin=”{StaticResource PhoneHorizontalMargin}”>
<Image x:Name=”ItemImage”/>
<TextBlock x:Name=”ItemTextBlock” Margin=”{StaticResource PhoneMargin}”
TextWrapping=”Wrap”/>
</StackPanel>
</ScrollViewer>
</Grid>
</phone:PhoneApplicationPage>

[/code]

Notes:

  • An Image element is used to display the appropriate image, just like the images that Ruler used in the preceding chapter. Its Source property must be set in order for something to be displayed, but this is done in the code-behind.
  • The text block is told to wrap its text to prevent it from going off the right edge of the screen.
  • The stack panel is placed in a scroll viewer, for the sake of the landscape orientations.

Listing 6.4 contains the code-behind for this page.

LISTING 6.4 DetailsPage.xaml.cs—The Code-Behind for the Baby Sign Language Details Page

[code]

using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class DetailsPage : PhoneApplicationPage
{
public DetailsPage()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Make sure the two indices are passed along
if (!this.NavigationContext.QueryString.ContainsKey(“categoryIndex”) ||
!this.NavigationContext.QueryString.ContainsKey(“signIndex”))
return;
// Convert the query string parameters into integers
int categoryIndex =
int.Parse(this.NavigationContext.QueryString[“categoryIndex”]);
int signIndex = int.Parse(this.NavigationContext.QueryString[“signIndex”]);
// Fetch the data
Sign sign = Data.Categories[categoryIndex].Signs[signIndex];
// Update the UI
this.PageTitle.Text = sign.Name;
this.ItemImage.Source = new BitmapImage(sign.ImageUri);
this.ItemTextBlock.Text = sign.Description;
}
}
}

[/code]

This implementation of OnNavigatedTo is similar to MainPage’s OnNavigatedTo method, except that it retrieves two values from the query string instead of one. Once it has both indices, it is able to retrieve the appropriate Sign instance from Data.Categories, and then it updates three pieces of UI with its three pieces of data.

Images

Although Listing 6.4 shows an image’s source being set to an instance of a BitmapImage constructed with a URI, in XAML all you need to do is set Source to the desired URI. For example:

[code]<Image Source=”Images/play.png”/>[/code]

Image only supports PNG and JPEG files. Unlike with application bar buttons, the URI used with Image can point to a file included in your project with a Build Action of Content or Resource. The URI can even point to an image on the Internet:

[code] <Image Source=”http://adamnathanapps.com/logo.png”/> [/code]

If you rotate the phone while an instance of DetailsPage is showing, something interesting happens with the image. As shown in Figure 6.6, it gets enlarged to continue filling the width of the page.

FIGURE 6.6 The image enlarges in a landscape orientation so it remains as wide as possible.
FIGURE 6.6 The image enlarges in a landscape orientation so it remains as wide as possible.

This behavior is caused by the Image element’s Stretch property, and this can be customized. Stretch can be set to one of the following values:

  • None—The image is always shown at its original dimensions.
  • Fill—The image fills the space given to it. Therefore, its aspect ratio may not be preserved.
  • Uniform (the default value)—The image is scaled as large as possible while still fitting entirely within the space given to it and preserving its aspect ratio. Therefore, there will be extra space in one dimension if its aspect ratio doesn’t match.
  • UniformToFill—The image is scaled to completely fill the space given to it while preserving its aspect ratio. Therefore, the content will be cropped in one dimension if its aspect ratio doesn’t match.

As with the alignment properties, the result of setting Stretch to these values depends on the space allocated by the element’s parent. Because the image in DetailsPage is placed in a vertical stack panel, setting its Stretch property to Fill, Uniform, and UniformToFill all behave identically, producing the result in Figure 6.6. When a smaller image is placed in a grid, however, the difference between all four values can be easily demonstrated, as shown in Figure 6.7.

FIGURE 6.7 Each of the four values for Image’s Stretch property.
FIGURE 6.7 Each of the four values for Image’s Stretch property.

The About Page

This app is the first of many to use a shared “about page” that displays something reasonable for any app that uses it, as seen back in Figure 6.1. The page requires the app’s name to be passed to it, and it uses hardcoded copyright information, but it uses two tricks to automatically display the app’s icon and the version number. Listing 6.5 shows its XAML.

LISTING 6.5 AboutPage.xaml—The User Interface for the About Page

[code]

<phone:PhoneApplicationPage x:Class=”WindowsPhoneApp.AboutPage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”PortraitOrLandscape”
shell:SystemTray.IsVisible=”True”>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard header, with some tweaks –>
<StackPanel Margin=”24,16,0,12”>
<TextBlock x:Name=”ApplicationName” Margin=”-1,0,0,0”
FontFamily=”{StaticResource PhoneFontFamilySemiBold}”
FontSize=”{StaticResource PhoneFontSizeMedium}”/>
<TextBlock Text=”about” Margin=”-3,-10,0,0”
FontFamily=”{StaticResource PhoneFontFamilySemiLight}”
FontSize=”{StaticResource PhoneFontSizeExtraExtraLarge}”/>
</StackPanel>
<!– A stacked tile icon, version #, and copyright –>
<ScrollViewer Grid.Row=”1”>
<StackPanel>
<Border Background=”{StaticResource PhoneAccentBrush}”
HorizontalAlignment=”Center”>
<Image Source=”/Background.png” Stretch=”None”/>
</Border>
<TextBlock x:Name=”VersionTextBlock” Margin=”24,8,24,0”/>
<TextBlock Margin=”24,36,24,0”>© 2011 Adam Nathan</TextBlock>
</StackPanel>
</ScrollViewer>
</Grid>
</phone:PhoneApplicationPage>

[/code]

The trick to display the icon is simply to grab the same image used for the tile icon, assuming it is the standard spot (the root of the project) with the standard name (Background.png). The URI starts with a forward slash to ensure that the file is retrieved from the root, even if the page is linked into the project at a nonroot folder. This works because Background.png must be included as a content file in order to work as a tile icon.

Listing 6.6 contains the code-behind for this page.

LISTING 6.6 AboutPage.xaml.cs—The Code-Behind for the About Page

[code]

using System.Windows.Navigation;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class AboutPage : PhoneApplicationPage
{
public AboutPage()
{
InitializeComponent();
// Fill out the version number
try
{
string s = typeof(AboutPage).Assembly.ToString();
if (s != null && s.IndexOf(“Version=”) >= 0)
{
s = s.Substring(s.IndexOf(“Version=”) + “Version=”.Length);
s = s.Substring(0, s.IndexOf(“,”));
this.VersionTextBlock.Text = “version “ + s;
}
}
catch { /* Never mind! */ }
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Set the application name in the header
if (this.NavigationContext.QueryString.ContainsKey(“appName”))
{
this.ApplicationName.Text =
this.NavigationContext.QueryString[“appName”].ToUpperInvariant();
}
}
}
}

[/code]

To display that app’s name, OnNavigatedTo does the now-familiar technique of grabbing it from the query string.

To display the version number, this code parses it out of the string returned by the current assembly’s ToString method. This string can look something like this:

[code]BabySignLanguage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null[/code]

Assembly has a GetName method that returns an object with a Version property, so it would have been better for the code to simply be:

[code]

VersionTextBlock.Text = “version “ +
typeof(AboutPage).Assembly.GetName().Version.ToString();

[/code]

However, apps do not have permission to call this (it returns more information than what you get from ToString), which is why the string parsing technique is used instead.

Data Binding

Although we’ve now seen a completely functional implementation of the Baby Sign Language app, the project included with this book has a few tweaks to make the code a bit nicer. It leverages data binding in a few more cases to reduce the amount C# code that needs to be written.

When data binding is used in the MainPage list box data template (the {Binding Name} string), it is implicitly given the context of the current list item so referencing the Name property has meaning. You can use this same data binding technique in other places, too, as long as you explicitly set the context.

Updating the Details Page

For example, the code-behind for DetailsPage in Listing 6.4 has this code inside of OnNavigatedTo:

[code]

// Update the UI
this.PageTitle.Text = sign.Name;
this.ItemImage.Source = new BitmapImage(sign.ImageUri);
this.ItemTextBlock.Text = sign.Description;

[/code]

We can move the setting of these three properties from C# to XAML if we change these three lines of code to one that sets the page’s DataContext property instead:

[code]

// Update the UI
this.DataContext = sign;

[/code]

DataContext is of type object, so it can be set to anything. When it is set to the sign variable, the special {Binding XXX} syntax can be used by any element on the page to refer to properties on this object. Therefore, the XAML for DetailsPage in Listing 6.3 can change as follows to leverage this data context:

[code]

<phone:PhoneApplicationPage …>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard header, with some tweaks –>
<StackPanel Margin=”24,16,0,12”>
<TextBlock Text=”BABY SIGN LANGUAGE” Margin=”-1,0,0,0”
FontFamily=”{StaticResource PhoneFontFamilySemiBold}”
FontSize=”{StaticResource PhoneFontSizeMedium}”/>
<TextBlock x:Name=”PageTitle” Text=”{Binding Name}” Margin=”-3,-10,0,0”
FontFamily=”{StaticResource PhoneFontFamilySemiLight}”
FontSize=”{StaticResource PhoneFontSizeExtraExtraLarge}”/>
</StackPanel>
<ScrollViewer Grid.Row=”1”>
<StackPanel Margin=”{StaticResource PhoneHorizontalMargin}”>
<Image x:Name=”ItemImage” Source=”{Binding ImageUri}”/>
<TextBlock x:Name=”ItemTextBlock” Text=”{Binding Description}”
Margin=”{StaticResource PhoneMargin}”
TextWrapping=”Wrap”/>
</StackPanel>
</ScrollViewer>
</Grid>
</phone:PhoneApplicationPage>

[/code]

Notice that the three elements with databound property values no longer need to be named, as they no longer need to be accessed from code-behind! Although this updated DetailsPage behaves identically to the original one, having the details of the data display captured entirely within the XAML file usually enables a more flexible and less error-prone development process compared to keeping things in sync across a XAML file and its code-behind file. And although the Binding syntax takes some getting used to, it usually results in fewer lines of code needing to be written.

Updating the Main Page

MainPage can also be updated to take advantage of data binding when filling the list box with its items. Listing 6.2 has the following implementation of the page’s Loaded event handler:

[code]

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (this.ListBox.Items.Count > 0)
return; // We already added the data.
// (We must be navigating back to this page.)
if (this.categoryIndex == -1)
{
// This is the root page. Fill the list box with the categories.
foreach (Category category in Data.Categories)
this.ListBox.Items.Add(category);
}
else
{
// This is a page for a specific category.
// Fill the list box with the category’s items.
foreach (Sign sign in Data.Categories[this.categoryIndex].Signs)
this.ListBox.Items.Add(sign);
}
}
In addition to its Items property, however, ListBox has an ItemsSource property. This
property (of type IEnumerable) can be used instead of Items to assign an entire collection
of data in one step, rather than filling its Items collection one-by-one:
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (this.ListBox.Items.Count > 0)
return; // We already added the data.
// (We must be navigating back to this page.)
if (this.categoryIndex == -1)
{
// This is the root page. Fill the list box with the categories.
this.ListBox.ItemsSource = Data.Categories;
}
else
{
// This is a page for a specific category.
// Fill the list box with the category’s items.
this.ListBox.ItemsSource = Data.Categories[this.categoryIndex].Signs;
}
}

[/code]

This reduces the amount of code by two lines, but it doesn’t buy us much. The advantage of using ItemsSource (and the reason for its existence) is that you can set its value via data binding. The final implementation of MainPage_Loaded sets the page’s DataContext property instead of directly setting ItemsSource:

[code]

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (this.ListBox.Items.Count > 0)
return; // We already added the data.
// (We must be navigating back to this page.)
if (this.categoryIndex == -1)
{
// This is the root page. Fill the list box with the categories.
this.DataContext = Data.Categories;
}
else
{
// This is a page for a specific category.
// Fill the list box with the category’s items.
this.DataContext = Data.Categories[this.categoryIndex].Signs;
}
}

[/code]

With this in place, the list box in MainPage.xaml (Listing 6.1) can be updated to set its ItemsSource to the entire DataContext object:

[code]

<ListBox x:Name=”ListBox” ItemsSource=”{Binding}”
SelectionChanged=”ListBox_SelectionChanged”>
<ListBox.ItemTemplate>

</ListBox.ItemTemplate>
</ListBox>

[/code]

When the Binding syntax is used without any property name, this indicates that the entire object should be used rather than the value of one of its properties.

The advantage of using data binding is not obvious in this case. Setting a list box’s ItemsSource with data binding enables it to provide performance optimizations when the list of data is large. Data binding also provides a number of convenient behaviors when the underlying data changes (seen in later chapters). Although neither of these is true for this small set of unchanging data, it’s a good habit to interact with a list box in this fashion.

The Finished Product

Baby Sign Language


Posted

in

by

Tags: