Blograby

TODO List (Pivot & Context Menu)

TODO List enables you to manage tasks in a fast, easy, and attractive way. Mark tasks with colored stars and/or detailed descriptions. Filter them in multiple ways, such as seeing overdue tasks, tasks due today, or tasks with stars. See what you’ve accomplished in a “done” list, with the ability to undo tasks. Trim the filters to only the ones you care about.

The main purpose of TODO List, however, is to demonstrate the pivot control. The pivot is one of the two signature user interface paradigms introduced by Windows Phone 7.

The Pivot Control

A pivot is basically a tabbed user interface in which you can swipe horizontally or tap one of the headers to switch to a different tab. This style of user interface is featured prominently in the built-in Mail, Calendar, and Settings apps, but also used by most of the other built-in apps: Internet Explorer, Maps, Marketplace, Music + Videos, People, and Pictures.

Pivots are designed to provide filtered views over the same set of data (as in the Mail app), distinct views over the same set of data (as in the Calendar app), or to provide easily switchable views over separate sets of data (such as application versus system settings in the Settings app). They are not meant to be used to expose sequential steps in a task, such as a wizard-style user interface. They are meant to occupy the entire page except, perhaps, for an application bar and/or status bar.

Just like the list box and list picker, pivot is an items control. Although the Pivot class exposes an Items collection to which any type of object can be added, only PivotItem objects or data objects should be added.

PivotItem is a simple content control with Content and Header properties of type object. Although these properties can be set to anything, Content is typically set to a panel such as a grid that contains a complex user interface, whereas Header is typically set to a string.

The pivot and panorama controls reside in an assembly that isn’t referenced by default in “Windows Phone Application”projects!

Although their .NET namespace (Microsoft.Phone.Controls) is the same as some commonly used types such as PhoneApplicationPage, the pivot and panorama controls are defined in the Microsoft.Phone.Controls assembly. (Controls such as PhoneApplicationPage are defined in the Microsoft.Phone assembly.) To use these controls, be sure to add a reference to Microsoft.Phone.Controls.dll. If you use Visual Studio’s “Windows Phone Pivot Application” or “Windows Phone Panorama Application”project templates, this assembly is already referenced by default. Similarly, if you add a “Windows Phone Pivot Page”or “Windows Phone Panorama Page” from Visual Studio’s Add New Item dialog, the assembly reference is automatically added to your project.

Here are three pivot design guidelines that apps should—but often don’t—obey:

A Pivot without PivotItems

Pivots are unusable with any UI elements other than PivotItem controls.The pivot attempts to render such elements as each item’s header and content.Therefore, attempting to use a different type of UI element throws an exception explaining,“Element is already the child of another element.”This is not a problem, however, because there’s no reason to not use PivotItems.They can contain anything, so you can always wrap your desired content with one.You can also add nonvisual data objects to a pivot and then use its ItemTemplate and HeaderTemplate properties to format them appropriately.

The Main Page

TODO List’s main page is the only one that uses a pivot. It contains five pivot items, all shown in Figure 26.1 in their empty states when first launching the app.

FIGURE 26.1 The five pivot items in their initial states.

The User Interface

Listing 26.1 contains the XAML for the main page.

LISTING 26.1 MainPage.xaml—The Main User Interface for TODO List

[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:controls=”clr-namespace:Microsoft.Phone.Controls;
➥assembly=Microsoft.Phone.Controls”
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”>
<!– The application bar, with 3 buttons and 1 menu item –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar>
<shell:ApplicationBarIconButton Text=”new”
IconUri=”/Shared/Images/appbar.add.png” Click=”AddButton_Click”/>
<shell:ApplicationBarIconButton Text=”instructions”
IconUri=”/Shared/Images/appbar.instructions.png”
Click=”InstructionsButton_Click”/>
<shell:ApplicationBarIconButton Text=”settings”
IconUri=”/Shared/Images/appbar.settings.png”
Click=”SettingsButton_Click”/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text=”about” Click=”AboutMenuItem_Click”/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<phone:PhoneApplicationPage.Resources>
<!– A data template shared by the first four list boxes –>
<DataTemplate x:Key=”DataTemplate”>
<StackPanel Orientation=”Horizontal” local:Tilt.IsEnabled=”True”>
<!– Add a context menu to the item –>
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu Opened=”ContextMenu_Opened”
Closed=”ContextMenu_Closed”>
<toolkit:MenuItem Header=”mark as done” Click=”MarkMenuItem_Click”/>
<toolkit:MenuItem Header=”edit” Click=”EditMenuItem_Click”/>
<toolkit:MenuItem Header=”delete” Click=”DeleteMenuItem_Click”/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<!– The star, with the item-specific color –>
<Rectangle Fill=”{Binding Star}” Width=”26” Height=”25” Margin=”0,0,0,10”>
<Rectangle.OpacityMask>
<ImageBrush ImageSource=”Images/star.png”/>
</Rectangle.OpacityMask>
</Rectangle>
<!– The title –>
<TextBlock Text=”{Binding Title}” Margin=”8,0,0,16”
Style=”{StaticResource PhoneTextExtraLargeStyle}”/>
</StackPanel>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
<controls:Pivot x:Name=”Pivot” Title=”TODO LIST”>
<!– Make the TODO LIST title match built-in apps better –>
<controls:Pivot.TitleTemplate>
<DataTemplate>
<TextBlock Text=”{Binding}” Margin=”-1,-1,0,-3”
Style=”{StaticResource PhoneTextTitle0Style}”/>
</DataTemplate>
</controls:Pivot.TitleTemplate>
<!– Pivot item #1 –>
<controls:PivotItem Header=”all”>
<Grid>
<TextBlock x:Name=”NoAllTextBlock” Text=”No tasks” Visibility=”Collapsed”
Margin=”22,17,0,0” Style=”{StaticResource PhoneTextGroupHeaderStyle}”/>
<ListBox x:Name=”AllListBox” ItemsSource=”{Binding}”
ItemTemplate=”{StaticResource DataTemplate}”
SelectionChanged=”ListBox_SelectionChanged”/>
</Grid>
</controls:PivotItem>
<!– Pivot item #2 –>
<controls:PivotItem x:Name=”TodayPivotItem” Header=”today”>
<Grid>
<TextBlock x:Name=”NoTodayTextBlock” Text=”Nothing is due today”
Visibility=”Collapsed” Margin=”22,17,0,0”
Style=”{StaticResource PhoneTextGroupHeaderStyle}”/>
<!– Show today’s date underneath the list box –>
<TextBlock x:Name=”TodayTextBlock” Opacity=”.2” Margin=”0,0,0,4”
HorizontalAlignment=”Right” VerticalAlignment=”Bottom” FontWeight=”Bold”
FontSize=”{StaticResource PhoneFontSizeExtraExtraLarge}”/>
<ListBox x:Name=”TodayListBox”
ItemTemplate=”{StaticResource DataTemplate}”
SelectionChanged=”ListBox_SelectionChanged”/>
</Grid>
</controls:PivotItem>
<!– Pivot item #3 –>
<controls:PivotItem x:Name=”PastDuePivotItem” Header=”past due”>
<Grid>
<TextBlock x:Name=”NoPastDueTextBlock” Visibility=”Collapsed”
Text=”Nothing is past due. Good job!” Margin=”22,17,0,0”
Style=”{StaticResource PhoneTextGroupHeaderStyle}”/>
<!– Show a clock underneath the list box –>
<Rectangle Opacity=”.2” Margin=”0,0,0,12” VerticalAlignment=”Bottom”
HorizontalAlignment=”Right” Width=”240” Height=”240”
Fill=”{StaticResource PhoneForegroundBrush}”>
<Rectangle.OpacityMask>
<ImageBrush ImageSource=”Images/clock.png”/>
</Rectangle.OpacityMask>
</Rectangle>
<ListBox x:Name=”PastDueListBox”
ItemTemplate=”{StaticResource DataTemplate}”
SelectionChanged=”ListBox_SelectionChanged”/>
</Grid>
</controls:PivotItem>
<!– Pivot item #4 –>
<controls:PivotItem x:Name=”StarredPivotItem” Header=”starred”>
<Grid>
<TextBlock x:Name=”NoStarredTextBlock” Text=”No starred tasks”
Visibility=”Collapsed” Margin=”22,17,0,0”
Style=”{StaticResource PhoneTextGroupHeaderStyle}”/>
<!– Show a star underneath the list box –>
<Rectangle Opacity=”.2” Margin=”0,0,0,12” VerticalAlignment=”Bottom”
HorizontalAlignment=”Right” Width=”240” Height=”240”
Fill=”{StaticResource PhoneForegroundBrush}”>
<Rectangle.OpacityMask>
<ImageBrush ImageSource=”Images/bigStar.png”/>
</Rectangle.OpacityMask>
</Rectangle>
<ListBox x:Name=”StarredListBox”
ItemTemplate=”{StaticResource DataTemplate}”
SelectionChanged=”ListBox_SelectionChanged”/>
</Grid>
</controls:PivotItem>
<!– Pivot item #5 –>
<controls:PivotItem x:Name=”DonePivotItem” Header=”done”>
<Grid>
<TextBlock x:Name=”NoDoneTextBlock” Text=”Nothing done. Get to work!”
Visibility=”Collapsed” Margin=”22,17,0,0”
Style=”{StaticResource PhoneTextGroupHeaderStyle}”/>
<!– Show a checkmark underneath the list box –>
<Rectangle Opacity=”.2” Margin=”0,0,0,12” VerticalAlignment=”Bottom”
HorizontalAlignment=”Right” Width=”277” Height=”240”
Fill=”{StaticResource PhoneForegroundBrush}”>
<Rectangle.OpacityMask>
<ImageBrush ImageSource=”Images/done.png”/>
</Rectangle.OpacityMask>
</Rectangle>
<ListBox x:Name=”DoneListBox” ItemsSource=”{Binding}”
SelectionChanged=”ListBox_SelectionChanged”>
<ListBox.ItemTemplate>
<!– A separate data template specific to the “done” list box –>
<DataTemplate>
<StackPanel Orientation=”Horizontal” Background=”Transparent”
local:Tilt.IsEnabled=”True”>
<!– Add a context menu to the item –>
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu Opened=”ContextMenu_Opened”
Closed=”ContextMenu_Closed”>
<toolkit:MenuItem Header=”unmark as done”
Click=”UnmarkMenuItem_Click”/>
<toolkit:MenuItem Header=”edit” Click=”EditMenuItem_Click”/>
<toolkit:MenuItem Header=”delete”
Click=”DeleteMenuItem_Click”/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<!– A checkmark-in-a-circle image –>
<Rectangle Width=”48” Height=”48”
Fill=”{StaticResource PhoneForegroundBrush}”>
<Rectangle.OpacityMask>
<ImageBrush ImageSource=”Shared/Images/normal.done.png”/>
</Rectangle.OpacityMask>
</Rectangle>
<Grid>
<StackPanel Orientation=”Horizontal” Margin=”8,0,0,0”>
<!– The star, with the item-specific color –>
<Rectangle Fill=”{Binding Star}” Width=”26” Height=”25”>
<Rectangle.OpacityMask>
<ImageBrush ImageSource=”Images/star.png”/>
</Rectangle.OpacityMask>
</Rectangle>
<!– The title –>
<TextBlock Text=”{Binding Title}” Margin=”8,0,0,6”
Style=”{StaticResource PhoneTextExtraLargeStyle}”
HorizontalAlignment=”Left” />
</StackPanel>
<!– A horizontal line on top of the title –>
<Line X1=”-2” X2=”800” Y1=”32” Y2=”32” StrokeThickness=”2”
Stroke=”{StaticResource PhoneForegroundBrush}”/>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</controls:PivotItem>
</controls:Pivot>
</phone:PhoneApplicationPage>

[/code]

FIGURE 26.2 The custom title template makes subtle changes to the default appearance of the pivot’s title.
FIGURE 26.3 The separate item template for the “done” list box adds check marks and a strikethrough effect.
FIGURE 26.4 The context menu, shown here for “Do the dishes,” exposes three additional actions for each task.

On Windows, it’s standard for a context menu to include an item’s default on-click action, and even show it in bold. On Windows Phone, context menus should not include the default on-tap action. Instead, context menu items should be reserved for additional actions that cannot be invoked by any other means on the page. For example, the context menus in Listing 26.1 do not list “view details” as one of the menu items because normal taps on each item already perform that action. Following this guideline not only makes the context menu behavior consistent with the phone’s built-in apps, but it also preserves precious screen real estate.

Although none of them are used by TODO List, pivot exposes several events that are useful for dynamic pivot items:

Pivot already delay-loads items that are more than one swipe away to improve startup time, but many popular apps use these events to improve performance even further with their own pivotitem virtualization scheme.

The Code-Behind

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

LISTING 26.2 MainPage.xaml.cs—The Code-Behind for TODO List’s Main Page

[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
{
bool isNavigatingAway;
bool isContextMenuOpen;
public MainPage()
{
InitializeComponent();
this.Loaded += MainPage_Loaded;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
this.isNavigatingAway = true;
base.OnNavigatedFrom(e);
// Remember the selected item
Settings.SelectedPivotItemName.Value =
(this.Pivot.SelectedItem as PivotItem).Name;
// Workaround for troubles when pivot items are removed and
// cause SelectedIndex > Count-1
this.Pivot.SelectedIndex = 0;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.isNavigatingAway = false;
base.OnNavigatedTo(e);
this.TodayTextBlock.Text = DateTime.Now.ToShortDateString();
// If the set of included pivot items were changed
// on the settings page, refresh them
int newPivotItemsCount = 1 + (Settings.IsTodayVisible.Value ? 1 : 0) +
(Settings.IsPastDueVisible.Value ? 1 : 0) +
(Settings.IsStarredVisible.Value ? 1 : 0) +
(Settings.IsDoneVisible.Value ? 1 : 0);
if (this.Pivot.Items.Count != newPivotItemsCount)
{
int insertLocation = 1;
ShowOrHidePivotItem(this.TodayPivotItem,
Settings.IsTodayVisible.Value, ref insertLocation);
ShowOrHidePivotItem(this.PastDuePivotItem,
Settings.IsPastDueVisible.Value, ref insertLocation);
ShowOrHidePivotItem(this.StarredPivotItem,
Settings.IsStarredVisible.Value, ref insertLocation);
ShowOrHidePivotItem(this.DonePivotItem,
Settings.IsDoneVisible.Value, ref insertLocation);
}
}
void ShowOrHidePivotItem(PivotItem item, bool show, ref int insertLocation)
{
// Insert or remove the pivot item, if necessary
if (show && item.Parent == null)
this.Pivot.Items.Insert(insertLocation, item);
else if (!show && item.Parent != null)
this.Pivot.Items.Remove(item);
if (show)
insertLocation++;
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (!isNavigatingAway) // Workaround for Loaded-raised-too-often bug
{
// Two of the list boxes use data binding
this.AllListBox.DataContext = Settings.TaskList.Value;
this.DoneListBox.DataContext = Settings.DoneList.Value;
// The rest are manually filled by filtering the task list
RefreshLists();
// Restore the selected item
// (done here because OnNavigatedTo is too early)
PivotItem pivotItem =
this.FindName(Settings.SelectedPivotItemName.Value) as PivotItem;
if (pivotItem != null)
this.Pivot.SelectedItem = pivotItem;
}
}
void RefreshLists()
{
DateTime today = DateTime.Now.Date;
// Fill the three filtered lists
this.TodayListBox.Items.Clear();
this.PastDueListBox.Items.Clear();
this.StarredListBox.Items.Clear();
foreach (Task item in Settings.TaskList.Value)
{
// today
if (item.DueDate.Date == today)
this.TodayListBox.Items.Add(item);
// past due
if (item.DueDate < DateTime.Now)
this.PastDueListBox.Items.Add(item);
// starred
if (item.Star != null && item.Star != “none”)
this.StarredListBox.Items.Add(item);
}
// Show/hide the “no tasks” labels
this.NoAllTextBlock.Visibility = Settings.TaskList.Value.Count == 0 ?
Visibility.Visible : Visibility.Collapsed;
this.NoTodayTextBlock.Visibility = this.TodayListBox.Items.Count == 0 ?
Visibility.Visible : Visibility.Collapsed;
this.NoPastDueTextBlock.Visibility = this.PastDueListBox.Items.Count == 0 ?
Visibility.Visible : Visibility.Collapsed;
this.NoStarredTextBlock.Visibility = this.StarredListBox.Items.Count == 0 ?
Visibility.Visible : Visibility.Collapsed;
this.NoDoneTextBlock.Visibility = Settings.DoneList.Value.Count == 0 ?
Visibility.Visible : Visibility.Collapsed;
}
void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (isContextMenuOpen)
{
// Cancel the selection
(sender as ListBox).SelectedIndex = -1;
return;
}
if (e.AddedItems.Count != 1)
return;
// Communicate the selected item to the details page
Settings.CurrentTask.Value = e.AddedItems[0] as Task;
// Navigate to the details page
this.NavigationService.Navigate(new Uri(“/DetailsPage.xaml”,
UriKind.Relative));
// Undo the selection so the same item can be tapped again upon return
(sender as ListBox).SelectedIndex = -1;
}
// Context menu handlers
void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
this.isContextMenuOpen = true;
}
void ContextMenu_Closed(object sender, RoutedEventArgs e)
{
this.isContextMenuOpen = false;
}
void MarkMenuItem_Click(object sender, RoutedEventArgs e)
{
Task task = (sender as MenuItem).DataContext as Task;
// Move from the task list to the done list
Settings.TaskList.Value.Remove(task);
Settings.DoneList.Value.Add(task);
RefreshLists();
}
void UnmarkMenuItem_Click(object sender, RoutedEventArgs e)
{
Task task = (sender as MenuItem).DataContext as Task;
// Move from the done list to the task list
Settings.DoneList.Value.Remove(task);
Settings.TaskList.Value.Add(task);
RefreshLists();
}
void EditMenuItem_Click(object sender, RoutedEventArgs e)
{
// Communicate the selected item to the add/edit page
Settings.CurrentTask.Value = (sender as MenuItem).DataContext as Task;
// Navigate to the add/edit page
this.NavigationService.Navigate(new Uri(“/AddEditPage.xaml”,
UriKind.Relative));
}
void DeleteMenuItem_Click(object sender, RoutedEventArgs e)
{
if (MessageBox.Show(
“Are you sure you want to permanently delete this task?”, “Delete task”,
MessageBoxButton.OKCancel) == MessageBoxResult.OK)
{
// The task is only in one of the two lists, but just try deleting from
// both rather than checking. One call will work, one will be a no-op.
Settings.TaskList.Value.Remove((sender as MenuItem).DataContext as Task);
Settings.DoneList.Value.Remove((sender as MenuItem).DataContext as Task);
RefreshLists();
}
}
// Application bar handlers
void AddButton_Click(object sender, EventArgs e)
{
Settings.CurrentTask.Value = null;
this.NavigationService.Navigate(new Uri(“/AddEditPage.xaml”,
UriKind.Relative));
}
void InstructionsButton_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/InstructionsPage.xaml”,
UriKind.Relative));
}
void SettingsButton_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/SettingsPage.xaml”,
UriKind.Relative));
}
void AboutMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(
“/Shared/About/AboutPage.xaml?appName=TODO List”, UriKind.Relative));
}
}
}

[/code]

Setting a pivot’s SelectedItem or SelectedIndex property always animates the change in selection!

This is extremely irritating for the main scenarios in which I can imagine these properties being used. For example, when an app is activated and you want to restore a pivot to its previous state (providing the illusion that it was running the whole time), you really want it to appear with the user’s previous selection instantly visible. A workaround to enable this behavior would be to physically shift the order of the pivot items, so the previous selection is always the 0th item and to not write any code that depends on indices.

Setting a pivot item’s visibility has no effect!

Temporarily hiding pivot items would be easy to do if you could simply set its Visibility property to Collapsed.Unfortunately, because this has no effect, the only way to hide a pivot item—and not have it occupy space—is to remove it from the pivot’s Items collection.

According to Windows Phone design guidelines, you should avoid removing empty pivot items if the user has some way to add information to it. Instead, you should show the empty pivot page or perhaps put an explanatory message in it, as done with each pivot item in TODO List’s main page.

Supporting Data Types

As seen in the preceding section, TODO List manipulates two collections of tasks exposed as settings. This involves three classes that are important to understand for appreciating how this app works. Listing 26.3 shows the implementation of the Task class used to represent each item shown in any of main page’s list boxes.

LISTING 26.3 Task.cs—The Type of Every Item in Every List Box

[code]

using System;
using System.ComponentModel;
namespace WindowsPhoneApp
{
public class Task : INotifyPropertyChanged
{
// The backing fields
string title;
string description;
string star;
DateTimeOffset createdDate;
DateTimeOffset modifiedDate;
DateTimeOffset dueDate;
// The properties, which raise change notifications
public string Title {
get { return this.title; }
set { this.title = value; OnPropertyChanged(“Title”); }
}
public string Description {
get { return this.description; }
set { this.description = value; OnPropertyChanged(“Description”); }
}
public string Star {
get { return this.star; }
set { this.star = value; OnPropertyChanged(“Star”); }
}
public DateTimeOffset CreatedDate {
get { return this.createdDate; }
set { this.createdDate = value; OnPropertyChanged(“CreatedDate”); }
}
public DateTimeOffset ModifiedDate {
get { return this.modifiedDate; }
set { this.modifiedDate = value; OnPropertyChanged(“ModifiedDate”); }
}
public DateTimeOffset DueDate {
get { return this.dueDate; }
set { this.dueDate = value; OnPropertyChanged(“DueDate”); }
}
void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
}

[/code]

Listing 26.4 contains the entire set of persisted settings used by TODO List.

LISTING 26.4 Settings.cs—All the Settings Persisted to Isolated Storage

[code]

using System.Collections.ObjectModel;
namespace WindowsPhoneApp
{
public static class Settings
{
// The selected pivot item, stored by name
public static readonly Setting<string> SelectedPivotItemName =
new Setting<string>(“SelectedPivotItemName”, “all”);
// Which pivot items are included in the pivot
public static readonly Setting<bool> IsTodayVisible =
new Setting<bool>(“IsTodayVisible”, true);
public static readonly Setting<bool> IsPastDueVisible =
new Setting<bool>(“IsPastDueVisible”, true);
public static readonly Setting<bool> IsStarredVisible =
new Setting<bool>(“IsStarredVisible”, true);
public static readonly Setting<bool> IsDoneVisible =
new Setting<bool>(“IsDoneVisible”, true);
// The task currently in the details or add/edit page
public static readonly Setting<Task> CurrentTask =
new Setting<Task>(“CurrentTask”, null);
// Sorted in chronological order
public static readonly Setting<SortedTaskCollection> TaskList =
new Setting<SortedTaskCollection>(“TaskList”,
new SortedTaskCollection());
// Kept in the order tasks get done
public static readonly Setting<ObservableCollection<Task>> DoneList =
new Setting<ObservableCollection<Task>>(“DoneList”,
new ObservableCollection<Task>());
}
}

[/code]

Listing 26.5 shows the implementation of this sorted collection class.

LISTING 26.5 SortedTaskCollection.cs—Adds Automatic Sorting to an Observable Collection of Tasks

[code]

using System;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
namespace WindowsPhoneApp
{
[CollectionDataContract]
public class SortedTaskCollection : ObservableCollection<Task>
{
protected override void InsertItem(int index, Task item)
{
// Ignore the index. Instead, keep the list sorted in chronological order
int i = 0;
for (i = 0; i < this.Count; i++)
{
DateTimeOffset d = this[i].DueDate;
if (d > item.DueDate)
break;
}
base.InsertItem(i, item);
}
}
}

[/code]

In addition to the CollectionDataContract attribute designed for collection classes, System.Runtime.Serialization exposes a DataContract attribute that can be used on regular (non-collection) classes.

Normally,when data fails to serialize to isolated storage or page state, your only indication is that the data does not exist when you next launch or activate the app.To be able to see detailed exception information for the serialization failure, run your app under Visual Studio’s debugger but tell it to catch all first-chance .NET exceptions.You can do this by checking the “Thrown” checkbox next to “Common Language Runtime Exceptions” on the Exceptions dialog (found under the Debug, Exceptions… menu item). When doing this, you might encounter several additional exceptions within the .NET Framework that are actually harmless and not related to the problems your app is experiencing.You’ll need to continue past those to see the relevant exception.

The Details Page

The details page, shown in Figure 26.5, is a straightforward display of each task’s properties. The task’s title is used as the page title, and the description and date properties are shown below it. If the item has been given a star, it is shown as well. For convenience, the page’s application bar exposes buttons for each of the three actions that the main page exposes as context menu items.

Listing 26.6 contains this page’s XAML and Listing 26.7 contains its code-behind.

LISTING 26.6 DetailsPage.xaml—The User Interface for TODO List’s 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”
xmlns:local=”clr-namespace:WindowsPhoneApp”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”PortraitOrLandscape” shell:SystemTray.IsVisible=”True”>
<!– The application bar, with 3 buttons and a menu item –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar>
<shell:ApplicationBarIconButton Text=”mark done”
IconUri=”/Images/appbar.markDone.png” Click=”MarkUnmarkButton_Click”/>
<shell:ApplicationBarIconButton Text=”edit”
IconUri=”/Shared/Images/appbar.edit.png” Click=”EditButton_Click”/>
<shell:ApplicationBarIconButton Text=”delete”
IconUri=”/Shared/Images/appbar.delete.png” Click=”DeleteButton_Click”/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text=”about” Click=”AboutMenuItem_Click”/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<phone:PhoneApplicationPage.Resources>
<local:DateConverter x:Key=”DateConverter”/>
</phone:PhoneApplicationPage.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard header –>
<StackPanel Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock Text=”TODO LIST” Style=”{StaticResource PhoneTextTitle0Style}”/>
<TextBlock Text=”{Binding Title}”
Style=”{StaticResource PhoneTextTitle1Style}”/>
</StackPanel>
<!– The appropriately-colored star –>
<Rectangle Grid.Row=”2” Width=”240” Height=”240” Fill=”{Binding Star}”
VerticalAlignment=”Bottom” HorizontalAlignment=”Right” Margin=”0,0,0,12”>
<Rectangle.OpacityMask>
<ImageBrush ImageSource=”Images/bigStar.png”/>
</Rectangle.OpacityMask>
</Rectangle>
<ScrollViewer Grid.Row=”1”>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=”Auto”/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”24”/>
</Grid.RowDefinitions>
<!– The description –>
<TextBlock Text=”{Binding Description}”
FontSize=”{StaticResource PhoneFontSizeLarge}”
Grid.ColumnSpan=”2” Margin=”24,0,24,24” TextWrapping=”Wrap”/>
<!– The 2-3 dates –>
<Rectangle x:Name=”AccentRectangle” Grid.Row=”1” Grid.ColumnSpan=”2”
Grid.RowSpan=”4” Fill=”{StaticResource PhoneAccentBrush}”/>
<TextBlock Grid.Row=”1” Text=”Due:” FontWeight=”Bold” Margin=”24,24,24,0”
FontSize=”{StaticResource PhoneFontSizeMedium}”/>
<TextBlock Grid.Row=”2” Text=”Created:” Margin=”24,0”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<TextBlock x:Name=”ModifiedLabelTextBlock” Grid.Row=”3” Text=”Modified:”
Margin=”24,0” Foreground=”{StaticResource PhoneSubtleBrush}”/>
<TextBlock Grid.Column=”1” Grid.Row=”1”
Text=”{Binding DueDate, Converter={StaticResource DateConverter}}”
Margin=”0,24,0,0” FontWeight=”Bold”
FontSize=”{StaticResource PhoneFontSizeMediumLarge}”/>
<TextBlock Grid.Column=”1” Grid.Row=”2”
Text=”{Binding CreatedDate, Converter={StaticResource DateConverter}}”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<TextBlock x:Name=”ModifiedTextBlock” Grid.Column=”1” Grid.Row=”3”
Text=”{Binding ModifiedDate, Converter={StaticResource DateConverter}}”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
</Grid>
</ScrollViewer>
</Grid>
</phone:PhoneApplicationPage>

[/code]

LISTING 26.7 DetailsPage.xaml.cs—The Code-Behind for TODO List’s Details Page

[code]

using System;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace WindowsPhoneApp
{
public partial class DetailsPage : PhoneApplicationPage
{
IApplicationBarIconButton markUnmarkButton;
public DetailsPage()
{
InitializeComponent();
this.markUnmarkButton = this.ApplicationBar.Buttons[0]
as IApplicationBarIconButton;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Set the context for the data binding done in XAML
this.DataContext = Settings.CurrentTask.Value;
if (Settings.CurrentTask.Value != null)
{
// Only show the modified date if different from the created date
if (Settings.CurrentTask.Value.CreatedDate ==
Settings.CurrentTask.Value.ModifiedDate)
{
this.ModifiedLabelTextBlock.Visibility = Visibility.Collapsed;
this.ModifiedTextBlock.Visibility = Visibility.Collapsed;
}
else
{
this.ModifiedLabelTextBlock.Visibility = Visibility.Visible;
this.ModifiedTextBlock.Visibility = Visibility.Visible;
}
// Ensure that the application bar button correctly represents whether
// this task is done
if (Settings.DoneList.Value.Contains(Settings.CurrentTask.Value))
{
this.markUnmarkButton.IconUri =
new Uri(“/Images/appbar.unmarkDone.png”, UriKind.Relative);
this.markUnmarkButton.Text = “undo”;
}
}
}
// Application bar handlers
void MarkUnmarkButton_Click(object sender, EventArgs e)
{
if (this.markUnmarkButton.Text == “mark done”)
{
this.markUnmarkButton.IconUri = new Uri(“/Images/appbar.unmarkDone.png”,
UriKind.Relative);
this.markUnmarkButton.Text = “undo”;
// Move the item from the task list to the done list
Settings.TaskList.Value.Remove(Settings.CurrentTask.Value);
Settings.DoneList.Value.Add(Settings.CurrentTask.Value);
}
else
{
this.markUnmarkButton.IconUri = new Uri(“/Images/appbar.markDone.png”,
UriKind.Relative);
this.markUnmarkButton.Text = “mark done”;
// Move the item from the done list to the task list
Settings.DoneList.Value.Remove(Settings.CurrentTask.Value);
Settings.TaskList.Value.Add(Settings.CurrentTask.Value);
}
}
void EditButton_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/AddEditPage.xaml”,
UriKind.Relative));
}
void DeleteButton_Click(object sender, EventArgs e)
{
if (MessageBox.Show(
“Are you sure you want to permanently delete this task?”, “Delete task”,
MessageBoxButton.OKCancel) == MessageBoxResult.OK)
{
// The task is only in one of the two lists, but just try deleting from
// both rather than checking. One call will work, one will be a no-op.
Settings.TaskList.Value.Remove(Settings.CurrentTask.Value);
Settings.DoneList.Value.Remove(Settings.CurrentTask.Value);
if (this.NavigationService.CanGoBack)
this.NavigationService.GoBack();
}
}
void AboutMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(
“/Shared/About/AboutPage.xaml?appName=TODO List”, UriKind.Relative));
}
}
}

[/code]

The Add/Edit Page

The add/edit page acts like two distinct pages—a page for adding a new task and a page for editing an existing task—but due to their enormous similarities, it is implemented as a single page. Figure 26.6 shows this page in both of its roles.

FIGURE 26.6 The add/edit page in its two different modes.

The User Interface

Listing 26.8 contains the XAML for the add/edit page.

LISTING 26.8 AddEditPage.xaml—The User Interface for TODO List’s Add/Edit Page

[code]

<phone:PhoneApplicationPage x:Class=”WindowsPhoneApp.AddItemPage”
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:sys=”clr-namespace:System;assembly=mscorlib”
xmlns:local=”clr-namespace:WindowsPhoneApp”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”PortraitOrLandscape” shell:SystemTray.IsVisible=”True”>
<!– The single-button, single-menu-item application bar –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar>
<shell:ApplicationBarIconButton Text=”save”
IconUri=”/Shared/Images/appbar.save.png” Click=”SaveButton_Click”/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text=”about” Click=”AboutMenuItem_Click”/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard header –>
<StackPanel Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock Text=”TODO LIST” Style=”{StaticResource PhoneTextTitle0Style}”/>
<TextBlock x:Name=”PageTitle”
Style=”{StaticResource PhoneTextTitle1Style}”/>
</StackPanel>
<ScrollViewer Grid.Row=”1”>
<StackPanel Margin=”{StaticResource PhoneHorizontalMargin}”>
<!– Title –>
<TextBlock Text=”Title” Margin=”11,0,0,-5”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<TextBox x:Name=”TitleTextBox” InputScope=”Text”
TextChanged=”TitleTextBox_TextChanged” KeyUp=”TitleTextBox_KeyUp”/>
<!– Description –>
<TextBlock Text=”Description” Margin=”11,11,0,-5”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<TextBox x:Name=”DescriptionTextBox” InputScope=”Text” MinHeight=”106”
AcceptsReturn=”True” TextWrapping=”Wrap”/>
<!– Star –>
<toolkit:ListPicker x:Name=”StarListPicker” Header=”Star”>
<toolkit:ListPicker.ItemTemplate>
<DataTemplate>
<StackPanel Orientation=”Horizontal”>
<!– Give each item the colored star next to its text –>
<Rectangle Fill=”{Binding}” Width=”26” Height=”25”>
<Rectangle.OpacityMask>
<ImageBrush ImageSource=”Images/star.png”/>
</Rectangle.OpacityMask>
</Rectangle>
<TextBlock Text=”{Binding}” Margin=”12 0 0 0”/>
</StackPanel>
</DataTemplate>
</toolkit:ListPicker.ItemTemplate>
<sys:String>none</sys:String>
<sys:String>red</sys:String>
<sys:String>yellow</sys:String>
<sys:String>green</sys:String>
<sys:String>blue</sys:String>
</toolkit:ListPicker>
<!– Due Date –>
<TextBlock Text=”Due Date” Margin=”11,11,0,-5” CacheMode=”BitmapCache”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<toolkit:DatePicker x:Name=”DueDatePicker” CacheMode=”BitmapCache”
ValueChanged=”DateTimePicker_ValueChanged”
local:Tilt.IsEnabled=”True”/>
<!– Due Time –>
<TextBlock Text=”Due Time” Margin=”11,11,0,-5” CacheMode=”BitmapCache”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<toolkit:TimePicker x:Name=”DueTimePicker” CacheMode=”BitmapCache”
ValueChanged=”DateTimePicker_ValueChanged”
local:Tilt.IsEnabled=”True”/>
</StackPanel>
</ScrollViewer>
</Grid>
</phone:PhoneApplicationPage>

[/code]

FIGURE 26.7 Each item in the list picker visually shows the star next to each color name, as seen when it is expanded.

The Code-Behind

Listing 26.9 contains the code-behind for the add/edit page.

LISTING 26.9 AddEditPage.xaml.cs—The Code-Behind for TODO List’s Add/Edit Page

[code]

using System;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace WindowsPhoneApp
{
public partial class AddItemPage : PhoneApplicationPage
{
IApplicationBarIconButton saveButton;
DateTime? pendingChosenDate;
DateTime? pendingChosenTime;
public AddItemPage()
{
InitializeComponent();
this.saveButton = this.ApplicationBar.Buttons[0]
as IApplicationBarIconButton;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Store the info in page state so it is preserved when temporarily
// navigating away from the page. This is especially important because the
// date picker and time picker navigate to a different page!
this.State[“Title”] = this.TitleTextBox.Text;
this.State[“Description”] = this.DescriptionTextBox.Text;
this.State[“Star”] = this.StarListPicker.SelectedItem;
this.State[“DueDate”] = this.DueDatePicker.Value;
this.State[“DueTime”] = this.DueTimePicker.Value;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// If we’re returning from the date or time picker,
// make sure we apply the chosen value
if (this.pendingChosenDate.HasValue)
this.State[“DueDate”] = this.pendingChosenDate;
if (this.pendingChosenTime.HasValue)
this.State[“DueTime”] = this.pendingChosenTime;
// Initialize the page for either add mode or edit mode
if (Settings.CurrentTask.Value == null)
{
this.PageTitle.Text = “new”;
}
else
{
this.PageTitle.Text = “edit”;
this.TitleTextBox.Text = Settings.CurrentTask.Value.Title;
this.DescriptionTextBox.Text = Settings.CurrentTask.Value.Description;
this.StarListPicker.SelectedItem = Settings.CurrentTask.Value.Star;
this.DueDatePicker.Value =
Settings.CurrentTask.Value.DueDate.LocalDateTime;
this.DueTimePicker.Value =
Settings.CurrentTask.Value.DueDate.LocalDateTime;
}
// Apply any temporary values from page state
if (this.State.ContainsKey(“Title”))
this.TitleTextBox.Text = (string)this.State[“Title”];
if (this.State.ContainsKey(“Description”))
this.DescriptionTextBox.Text = (string)this.State[“Description”];
if (this.State.ContainsKey(“Star”))
this.StarListPicker.SelectedItem = (string)this.State[“Star”];
if (this.State.ContainsKey(“DueDate”))
this.DueDatePicker.Value = (DateTime?)this.State[“DueDate”];
if (this.State.ContainsKey(“DueTime”))
this.DueTimePicker.Value = (DateTime?)this.State[“DueTime”];
// Only allow saving when there’s a title
this.saveButton.IsEnabled = (this.TitleTextBox.Text != null &&
this.TitleTextBox.Text.Trim().Length > 0);
}
void TitleTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
// Only allow saving when there’s a title
this.saveButton.IsEnabled = (this.TitleTextBox.Text != null &&
this.TitleTextBox.Text.Trim().Length > 0);
}
void TitleTextBox_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
this.DescriptionTextBox.Focus();
}
void DateTimePicker_ValueChanged(object sender,
DateTimeValueChangedEventArgs e)
{
// Prevent the values from getting clobbered when navigating back
this.pendingChosenDate = this.DueDatePicker.Value;
this.pendingChosenTime = this.DueTimePicker.Value;
}
// Application bar handlers
void SaveButton_Click(object sender, EventArgs e)
{
// Consolidate the due date and due time into a single DateTime.
// First get just the date (no time) from the date picker’s value
DateTime dueDate = this.DueDatePicker.Value.Value.Date;
// Now add the time to this date
if (this.DueTimePicker.Value.HasValue)
dueDate = dueDate.AddMinutes(
this.DueTimePicker.Value.Value.TimeOfDay.TotalMinutes);
Task item = new Task
{
Title = this.TitleTextBox.Text.Trim(),
Description = this.DescriptionTextBox.Text.Trim(),
Star = (string)this.StarListPicker.SelectedItem,
ModifiedDate = DateTime.Now,
DueDate = dueDate
};
if (Settings.CurrentTask.Value != null)
{
// This is an edit
// Perform the edit differently for the task list versus done list
if (Settings.TaskList.Value.Remove(Settings.CurrentTask.Value))
{
// We removed the old item, and now let’s insert the new item.
// If the due date has changed, this re-sorts the list correctly.
// Be sure to give this new item the original created date
item.CreatedDate = Settings.CurrentTask.Value.CreatedDate;
Settings.TaskList.Value.Add(item);
Settings.CurrentTask.Value = item;
}
else
{
// We don’t want to change the ordering in the done list,
// so just update the item in-place
Settings.CurrentTask.Value.Title = item.Title;
Settings.CurrentTask.Value.Description = item.Description;
Settings.CurrentTask.Value.Star = item.Star;
Settings.CurrentTask.Value.ModifiedDate = item.ModifiedDate;
Settings.CurrentTask.Value.DueDate = item.DueDate;
// Don’t change CreatedDate!
}
}
else
{
// This is a new task
item.CreatedDate = item.ModifiedDate;
Settings.TaskList.Value.Add(item);
}
if (this.NavigationService.CanGoBack)
this.NavigationService.GoBack();
}
void AboutMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(
“/Shared/About/AboutPage.xaml?appName=TODO List”, UriKind.Relative));
}
}
}

[/code]

FIGURE 26.8 The settings page enables the user to hide all but the first pivot item.

The Settings Page

The settings page, shown in Figure 26.8, enables the user to turn off any of the pivot items except for the “all” item. (The “all” check box is present but always disabled to make it clear that this item can’t be hidden.) Listing 26.10 contains the XAML, and Listing 26.11 contains the code-behind.

LISTING 26.10 SettingsPage.xaml—The User Interface for TODO List’s Settings Page

[code]

<phone:PhoneApplicationPage x:Class=”WindowsPhoneApp.SettingsPage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”
xmlns:local=”clr-namespace:WindowsPhoneApp”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”PortraitOrLandscape” shell:SystemTray.IsVisible=”True”>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard header –>
<StackPanel Grid.Row=”0” Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock Text=”SETTINGS” Style=”{StaticResource PhoneTextTitle0Style}”/>
<TextBlock Text=”todo list” Style=”{StaticResource PhoneTextTitle1Style}”/>
</StackPanel>
<!– A check box for each setting –>
<ScrollViewer Grid.Row=”1”>
<StackPanel Margin=”{StaticResource PhoneHorizontalMargin}”>
<TextBlock Margin=”12,12,0,0” Text=”Visible Lists”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<CheckBox Content=”all” IsEnabled=”False” IsChecked=”True”
FontSize=”{StaticResource PhoneFontSizeExtraLarge}”
local:Tilt.IsEnabled=”True”/>
<CheckBox x:Name=”TodayCheckBox” Content=”today”
FontSize=”{StaticResource PhoneFontSizeExtraLarge}”
local:Tilt.IsEnabled=”True”/>
<CheckBox x:Name=”PastDueCheckBox” Content=”past due”
FontSize=”{StaticResource PhoneFontSizeExtraLarge}”
local:Tilt.IsEnabled=”True”/>
<CheckBox x:Name=”StarredCheckBox” Content=”starred”
FontSize=”{StaticResource PhoneFontSizeExtraLarge}”
local:Tilt.IsEnabled=”True”/>
<CheckBox x:Name=”DoneCheckBox” Content=”done” local:Tilt.IsEnabled=”True”
FontSize=”{StaticResource PhoneFontSizeExtraLarge}”/>
</StackPanel>
</ScrollViewer>
</Grid>
</phone:PhoneApplicationPage>

[/code]

LISTING 26.11 SettingsPage.xaml.cs—The Code-Behind for TODO List’s Settings Page

[code]

using System.Windows.Navigation;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class SettingsPage : PhoneApplicationPage
{
public SettingsPage()
{
InitializeComponent();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Save the settings
Settings.IsTodayVisible.Value = this.TodayCheckBox.IsChecked.Value;
Settings.IsPastDueVisible.Value = this.PastDueCheckBox.IsChecked.Value;
Settings.IsStarredVisible.Value = this.StarredCheckBox.IsChecked.Value;
Settings.IsDoneVisible.Value = this.DoneCheckBox.IsChecked.Value;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Respect the saved settings
this.TodayCheckBox.IsChecked = Settings.IsTodayVisible.Value;
this.PastDueCheckBox.IsChecked = Settings.IsPastDueVisible.Value;
this.StarredCheckBox.IsChecked = Settings.IsStarredVisible.Value;
this.DoneCheckBox.IsChecked = Settings.IsDoneVisible.Value;
}
}
}

[/code]

This page is about as simple as a settings page can get. The hard part is supporting the hiding of pivot items that is done in the main page!

The Finished Product

Exit mobile version