Blograby

Passwords & Secrets (Encryption & Observable Collections)

Passwords & Secrets is a notepad-style app that you can protect with a master password. Therefore, it’s a great app for storing a variety of passwords and other secrets that you don’t want getting into the wrong hands. The note-taking functionality is top-notch, supporting

On top of this, the data in each note is encrypted with 256-bit Advanced Encryption Standard (AES) encryption to keep prying eyes from discovering the data. This encryption is done based on the master password, so it’s important that the user never forgets their password! There is no way for the app to retrieve the data without it, as the app does not store the password for security reasons.

To make management of the master password as easy as possible, Passwords & Secrets supports specifying and showing a password hint. It also enables you to change your password (but only if you know the current password).

Why would I need to encrypt data stored in isolated storage? Isn’t my app the only thing that can access it?

Barring any bugs in the Windows Phone OS, another app should never be able to read your app’s isolated storage. And nobody should be able to remotely peer into your isolated storage. But if skilled hackers get physical access to your phone, they could certainly read the data stored on it. Encryption makes it virtually impossible for hackers to make any sense of the stored data.

Basic Cryptography

Silverlight’s System.Security.Cryptography namespace contains quite a bit of functionality for cryptographic tasks. This app wraps the necessary pieces of functionality from this namespace in order to expose an easy-to-use Crypto class. This class exposes two simple methods—Encrypt and Decrypt—that accept the decrypted/encrypted data along with a password to use as the basis for the encryption and decryption. Listing 21.1 contains the implementation.

LISTING 21.1 Crypto.cs—The Crypto Class That Exposes Simple Encrypt and Decrypt Methods

[code]

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace WindowsPhoneApp
{
public static class Crypto
{
public static string Encrypt(string data, string password)
{
if (data == null)
return null;
using (SymmetricAlgorithm algorithm = GetAlgorithm(password))
using (MemoryStream memoryStream = new MemoryStream())
using (CryptoStream cryptoStream = new CryptoStream(
memoryStream, algorithm.CreateEncryptor(), CryptoStreamMode.Write))
{
// Convert the original data to bytes then write them to the CryptoStream
byte[] buffer = Encoding.UTF8.GetBytes(data);
cryptoStream.Write(buffer, 0, buffer.Length);
cryptoStream.FlushFinalBlock();
// Convert the encrypted bytes back into a string
return Convert.ToBase64String(memoryStream.ToArray());
}
}
public static string Decrypt(string data, string password)
{
if (data == null)
return null;
using (SymmetricAlgorithm algorithm = GetAlgorithm(password))
using (MemoryStream memoryStream = new MemoryStream())
using (CryptoStream cryptoStream = new CryptoStream(
memoryStream, algorithm.CreateDecryptor(), CryptoStreamMode.Write))
{
// Convert the encrypted string to bytes then write them
// to the CryptoStream
byte[] buffer = Convert.FromBase64String(data);
cryptoStream.Write(buffer, 0, buffer.Length);
cryptoStream.FlushFinalBlock();
// Convert the original data back to a string
buffer = memoryStream.ToArray();
return Encoding.UTF8.GetString(buffer, 0, buffer.Length);
}
}
// Hash the input data with a salt, typically used for storing a password
public static string Hash(string data)
{
// Convert the data to bytes
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
// Create a new array with the salt bytes followed by the data bytes
byte[] allBytes = new byte[Settings.Salt.Value.Length + dataBytes.Length];
// Copy the salt at the beginning
Settings.Salt.Value.CopyTo(allBytes, 0);
// Copy the data after the salt
dataBytes.CopyTo(allBytes, Settings.Salt.Value.Length);
// Compute the hash for the combined set of bytes
byte[] hash = new SHA256Managed().ComputeHash(allBytes);
// Convert the bytes into a string
return Convert.ToBase64String(hash);
}
public static byte[] GenerateNewSalt(int length)
{
Byte[] bytes = new Byte[length];
// Fill the array with random bytes, using a cryptographic
// random number generator (RNG)
new RNGCryptoServiceProvider().GetBytes(bytes);
return bytes;
}
static SymmetricAlgorithm GetAlgorithm(string password)
{
// Use the Advanced Encryption Standard (AES) algorithm
AesManaged algorithm = new AesManaged();
// Derive an encryption key from the password
Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password,
Settings.Salt.Value);
// Initialize, converting the two values in bits to bytes (dividing by 8)
algorithm.Key = bytes.GetBytes(algorithm.KeySize / 8);
algorithm.IV = bytes.GetBytes(algorithm.BlockSize / 8);
return algorithm;
}
}
}

[/code]

Salt in Cryptography

Using salt can provide a number of benefits for slowing down hackers, especially when the salt can be kept a secret. In this app, although a salt must be passed to the constructor of Rfc2898DeriveBytes, it doesn’t really add value because the salt must be stored along with the encrypted data.The same goes for the salting of the hash inside the Hash function. Although this is good practice for a server managing multiple passwords (so dictionary-based attacks must be regenerated for each user, and so users with the same password won’t have the same hash), it is done in this app mainly for show.

The LoginControl User Control

With the Crypto class in place, we can create a login control that handles all the user interaction needed for the app’s master password. The LoginControl user control used by this app is shown in Figure 21.1. It has three different modes:

FIGURE 21.1 The three modes of the LoginControl user control in action.

Listing 21.2 contains the XAML for this control.

LISTING 21.2 LoginControl.xaml—The User Interface for the LoginControl User Control

[code]

<UserControl x:Class=”WindowsPhoneApp.LoginControl”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:local=”clr-namespace:WindowsPhoneApp”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”>
<Grid Background=”{StaticResource PhoneBackgroundBrush}”>
<!– A dim accent-colored padlock image –>
<Rectangle Fill=”{StaticResource PhoneAccentBrush}” Width=”300” Height=”364”
VerticalAlignment=”Bottom” HorizontalAlignment=”Right”
Margin=”{StaticResource PhoneMargin}” Opacity=”.5”>
<Rectangle.OpacityMask>
<ImageBrush ImageSource=”Images/lock.png”/>
</Rectangle.OpacityMask>
</Rectangle>
<ScrollViewer>
<Grid>
<!– This panel is used for both New User and Change Password modes –>
<StackPanel x:Name=”ChangePasswordPanel” Visibility=”Collapsed”
Margin=”{StaticResource PhoneMargin}”>
<!– Welcome! –>
<TextBlock x:Name=”WelcomeTextBlock” Visibility=”Collapsed”
Margin=”{StaticResource PhoneHorizontalMargin}” TextWrapping=”Wrap”>
<Run FontWeight=”Bold”>Welcome!</Run>
<LineBreak/>
Choose a password that you’ll remember. There is no way to recover …
</TextBlock>
<!– Old password –>
<TextBlock Text=”Old password” x:Name=”OldPasswordLabel”
Style=”{StaticResource LabelStyle}”/>
<PasswordBox x:Name=”OldPasswordBox” KeyUp=”PasswordBox_KeyUp”/>
<!– New password –>
<TextBlock Text=”New password” Style=”{StaticResource LabelStyle}”/>
<PasswordBox x:Name=”NewPasswordBox” KeyUp=”PasswordBox_KeyUp”/>
<!– Confirm new password –>
<TextBlock Text=”Type new password again”
Style=”{StaticResource LabelStyle}”/>
<PasswordBox x:Name=”ConfirmNewPasswordBox” KeyUp=”PasswordBox_KeyUp”/>
<!– Password hint –>
<TextBlock Text=”Password hint (optional)”
Style=”{StaticResource LabelStyle}”/>
<TextBox x:Name=”PasswordHintTextBox” InputScope=”Text”
KeyUp=”PasswordBox_KeyUp”/>
<Button Content=”ok” Click=”OkButton_Click” MinWidth=”226”
HorizontalAlignment=”Left” Margin=”0,12,0,0”
local:Tilt.IsEnabled=”True”/>
</StackPanel>
<!– This panel is used only for the Normal Login mode –>
<StackPanel x:Name=”NormalLoginPanel” Visibility=”Collapsed”
Margin=”{StaticResource PhoneMargin}”>
<TextBlock Text=”Enter your password”
Style=”{StaticResource LabelStyle}”/>
<PasswordBox x:Name=”NormalLoginPasswordBox” KeyUp=”PasswordBox_KeyUp”/>
<Button Content=”ok” Click=”OkButton_Click” MinWidth=”226”
HorizontalAlignment=”Left” local:Tilt.IsEnabled=”True”/>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</UserControl>

[/code]

Listing 21.3 contains the code-behind.

LISTING 21.3 LoginControl.xaml.cs—The Code-Behind for the LoginControl User Control

[code]

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WindowsPhoneApp
{
public partial class LoginControl : UserControl
{
// A custom event
public event EventHandler Closed;
public LoginControl()
{
InitializeComponent();
// Update the UI depending on which of the three modes we’re in
if (Settings.HashedPassword.Value == null)
{
// The “new user” mode
this.WelcomeTextBlock.Visibility = Visibility.Visible;
this.OldPasswordLabel.Visibility = Visibility.Collapsed;
this.OldPasswordBox.Visibility = Visibility.Collapsed;
this.ChangePasswordPanel.Visibility = Visibility.Visible;
}
else if (CurrentContext.IsLoggedIn)
{
// The “change password” mode
this.ChangePasswordPanel.Visibility = Visibility.Visible;
}
else
{
// The “normal login” mode
this.NormalLoginPanel.Visibility = Visibility.Visible;
}
}
void OkButton_Click(object sender, RoutedEventArgs e)
{
string currentHashedPassword = Settings.HashedPassword.Value;
if (currentHashedPassword != null && !CurrentContext.IsLoggedIn)
{
// We’re in “normal login” mode
// If the hash of the attempted password matches the stored hash,
// then we know the user entered the correct password.
if (Crypto.Hash(this.NormalLoginPasswordBox.Password)
!= currentHashedPassword)
{
MessageBox.Show(“”, “Incorrect password”, MessageBoxButton.OK);
return;
}
// Keep the unencrypted password in-memory,
// only until this app is deactivated/closed
CurrentContext.Password = this.NormalLoginPasswordBox.Password;
}
else
{
// We’re in “new user” or “change password” mode
// For “change password,” be sure that the old password is correct
if (CurrentContext.IsLoggedIn && Crypto.Hash(this.OldPasswordBox.Password)
!= currentHashedPassword)
{
MessageBox.Show(“”, “Incorrect old password”, MessageBoxButton.OK);
return;
}
// Now validate the new password
if (this.NewPasswordBox.Password != this.ConfirmNewPasswordBox.Password)
{
MessageBox.Show(“The two passwords don’t match. Please try again.”,
“Oops!”, MessageBoxButton.OK);
return;
}
string newPassword = this.NewPasswordBox.Password;
if (newPassword == null || newPassword.Length == 0)
{
MessageBox.Show(“The password cannot be empty. Please try again.”,
“Nice try!”, MessageBoxButton.OK);
return;
}
// Store a hash of the password so we can check for the correct
// password in future logins without storing the actual password
Settings.HashedPassword.Value = Crypto.Hash(newPassword);
// Store the password hint as plain text
Settings.PasswordHint.Value = this.PasswordHintTextBox.Text;
// Keep the unencrypted password in-memory,
// only until this app is deactivated/closed
CurrentContext.Password = newPassword;
// If there already was a password, we must decrypt all data with the old
// password (then re-encrypt it with the new password) while we still
// know the old password! Otherwise the data will be unreadable!
if (currentHashedPassword != null)
{
// Each item in the NotesList setting has an EncryptedContent property
// that must be processed
for (int i = 0; i < Settings.NotesList.Value.Count; i++)
{
// Encrypt with the new password the data that is decrypted
// with the old password
Settings.NotesList.Value[i].EncryptedContent =
Crypto.Encrypt(
Crypto.Decrypt(Settings.NotesList.Value[i].EncryptedContent,
this.OldPasswordBox.Password),
newPassword
);
}
}
}
CurrentContext.IsLoggedIn = true;
Close();
}
void PasswordBox_KeyUp(object sender, KeyEventArgs e)
{
// Allow the Enter key to cycle between text boxes and to press the ok
// button when on the last text box
if (e.Key == Key.Enter)
{
if (sender == this.PasswordHintTextBox ||
sender == this.NormalLoginPasswordBox)
OkButton_Click(sender, e);
else if (sender == this.OldPasswordBox)
this.NewPasswordBox.Focus();
else if (sender == this.NewPasswordBox)
this.ConfirmNewPasswordBox.Focus();
else if (sender == this.ConfirmNewPasswordBox)
this.PasswordHintTextBox.Focus();
}
}
public void Close()
{
if (this.Visibility == Visibility.Collapsed)
return; // Already closed
// Clear all
this.OldPasswordBox.Password = “”;
this.NewPasswordBox.Password = “”;
this.ConfirmNewPasswordBox.Password = “”;
this.NormalLoginPasswordBox.Password = “”;
The LoginControl User Control 503
this.PasswordHintTextBox.Text = “”;
// Close by becoming invisible
this.Visibility = Visibility.Collapsed;
// Raise the event
if (this.Closed != null)
this.Closed(this, EventArgs.Empty);
}
}
}

[/code]

The Change Password Page

The change password page, seen previously in Figure 21.1, is nothing more than a page hosting a LoginControl instance. The user can only reach this page when already signed in, so the control is automatically initialized to the “change password” mode thanks to the code in Listing 21.3. Listings 21.4 and 21.5 contain the simple XAML and codebehind for the change password page.

LISTING 21.4 ChangePasswordPage.xaml—The User Interface for Password & Secrets’ Change Password Page

[code]
<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.ChangePasswordPage”
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 Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock Text=”PASSWORDS &amp; SECRETS”
Style=”{StaticResource PhoneTextTitle0Style}”/>
<TextBlock Text=”change password”
Style=”{StaticResource PhoneTextTitle1Style}”/>
</StackPanel>
<!– The user control –>
<local:LoginControl Grid.Row=”1” Closed=”LoginControl_Closed”/>
</Grid>
</phone:PhoneApplicationPage>

[/code]

LISTING 21.5 ChangePasswordPage.xaml.cs—The Code-Behind for Password & Secrets’ Change Password Page

[code]

using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class ChangePasswordPage : PhoneApplicationPage
{
public ChangePasswordPage()
{
InitializeComponent();
}
void LoginControl_Closed(object sender, System.EventArgs e)
{
if (this.NavigationService.CanGoBack)
this.NavigationService.GoBack();
}
}
}

[/code]

The Main Page

This app’s main page contains the list of user’s notes, as demonstrated in Figure 21.2. Each one can be tapped to view and/or edit it. A button on the application bar enables adding new notes. But before the list is populated and any of this is shown, the user must enter the correct password. When the user isn’t logged in, the LoginControl covers the entire page except its header, and the application bar doesn’t have the add-note button.

FIGURE 21.2 A list of notes on the main page, in various colors and sizes.

The User Interface

Listing 21.6 contains the XAML for the main page.

 

LISTING 21.6 MainPage.xaml—The User Interface for Password & Secrets’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”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”PortraitOrLandscape” shell:SystemTray.IsVisible=”True”>
<phone:PhoneApplicationPage.Resources>
<local:DateConverter x:Key=”DateConverter”/>
</phone:PhoneApplicationPage.Resources>
<!– The application bar, with 3 menu items –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text=”show password hint”
Click=”PasswordMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”instructions”
Click=”InstructionsMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”about” Click=”AboutMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”more apps”
Click=”MoreAppsMenuItem_Click”/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<Grid Background=”Transparent”>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard header –>
<StackPanel Grid.Row=”0”
Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock Text=”PASSWORDS &amp; SECRETS”
Style=”{StaticResource PhoneTextTitle0Style}”/>
</StackPanel>
<!– Show this when there are no notes –>
<TextBlock Name=”NoItemsTextBlock” Grid.Row=”1” Text=”No notes”
Visibility=”Collapsed” Margin=”22,17,0,0”
Style=”{StaticResource PhoneTextGroupHeaderStyle}”/>
<!– The list box containing notes –>
<ListBox x:Name=”ListBox” Grid.Row=”1” ItemsSource=”{Binding}”
SelectionChanged=”ListBox_SelectionChanged”>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<!– The title, in a style matching the note –>
<Border Background=”{Binding ScreenBrush}” Margin=”24,0” Width=”800”
MinHeight=”60” local:Tilt.IsEnabled=”True”>
<TextBlock Text=”{Binding Title}” FontSize=”{Binding TextSize}”
Foreground=”{Binding TextBrush}” Margin=”12”
VerticalAlignment=”Center”/>
</Border>
<!– The modified date –>
<TextBlock Foreground=”{StaticResource PhoneSubtleBrush}”
Text=”{Binding Modified, Converter={StaticResource DateConverter}}”
Margin=”24,0,0,12”/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!– The user control –>
<local:LoginControl x:Name=”LoginControl” Grid.Row=”1”
Closed=”LoginControl_Closed”/>
</Grid>
</phone:PhoneApplicationPage>

[/code]

Value Converters

In data binding, value converters can morph a source value into a completely different target value. They enable you to plug in custom logic without giving up the benefits of data binding. Value converters are often used to reconcile a source and target that are different data types. For example, you could change the background or foreground color of an element based on the value of some nonbrush data source, à la conditional formatting in Microsoft Excel. As another example, the toggle switch in the Silverlight for Windows Phone Toolkit leverages a value converter called OnOffConverter that converts the nullable Boolean IsChecked value to an “On” or “Off” string used as its default content.

In Passwords & Secrets, we want to slightly customize the display of each note’s Modified property. Modified is of type DateTimeOffset, so without a value converter applied, it would appear as follows:

[code]12/11/2012 10:18:49 PM -08:00[/code]

The -08:00 represents the time zone. It is expressed as an offset from Coordinated Universal Time (UTC).

Our custom value converter strips off the time zone information and the seconds, as that’s more information than we need. It produces a result like the following:

[code]12/11/2010 10:18 PM[/code]

Even if Modified were a DateTime instead of a DateTimeOffset, the value converter would still be useful for stripping the seconds value out of the string.

What’s the difference between the DateTime data type and DateTimeOffset?

Whereas DateTime refers to a logical point in time that is independent of any time zone, DateTimeOffset is a real point in time with an offset relative to the UTC time zone. In this app, DateTimeOffset is appropriate to use for the modified time of each note because users shouldn’t expect that point in time to change even if they later travel to a different time zone.The preceding chapter’s Alarm Clock, however, appropriately uses DateTime for the alarm time. Imagine that you set the alarm while in one time zone but you’re in a different time zone when it’s time for it to go off. If you had set your alarm for 8:00 AM, you probably expect it to go off at 8:00 AM no matter what time zone you happen to be in at the time. For most scenarios, using DateTimeOffset is preferable to DateTime.However, it was introduced into the .NET Framework years after DateTime, so the better name was already taken. (Designers of the class rejected calling it DateTime2 or DateTimeEx). Fortunately, consumers of these data types can pretty much use them interchangeably.

To create a value converter, you must write a class that implements an IValueConverter interface in the System.Windows.Data namespace. This interface has two simple methods—Convert, which is passed the source instance that must be converted to the target instance, and ConvertBack, which does the opposite. Listing 21.7 contains the implementation of the DateConverter value converter used in Listing 21.6.

LISTING 21.7 DateConverter.cs—A Value Converter That Customizes the Display of a DateTimeOffset

[code]

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace WindowsPhoneApp
{
public class DateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
DateTimeOffset date = (DateTimeOffset)value;
// Return a custom format
return date.LocalDateTime.ToShortDateString() + “ “
+ date.LocalDateTime.ToShortTimeString();
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
}

[/code]

The Convert method is called every time the source value changes. It’s given the DateTimeOffset value and returns a string with the date and time in a short format. The ConvertBack method is not needed, as it is only invoked in two-way data binding. Therefore, it returns a dummy value.

Value converters can be applied to any data binding with its Converter parameter. This was done in Listing 21.6 as follows:

[code]

<!– The modified date –>
<TextBlock Foreground=”{StaticResource PhoneSubtleBrush}”
Text=”{Binding Modified, Converter={StaticResource DateConverter}}”
Margin=”24,0,0,12”/>

[/code]

Setting this via StaticResource syntax requires an instance of the converter class to be defined in an appropriate resource dictionary. Listing 21.6 added an instance with the DateConverter key to the page’s resource dictionary:

[code]

<phone:PhoneApplicationPage.Resources>
<local:DateConverter x:Key=”DateConverter”/>
</phone:PhoneApplicationPage.Resources>

[/code]

Additional Data for Value Converters

The methods of IValueConverter are passed a parameter and a culture. By default, parameter is set to null and culture is set to the value of the target element’s Language property. However, the consumer of bindings can control these two values via Binding.ConverterParameter and Binding.ConverterCulture. For example:

[code]

<!– The modified date –>
<TextBlock Foreground=”{StaticResource PhoneSubtleBrush}”
Text=”{Binding Modified, Converter={StaticResource DateConverter},
ConverterParameter=custom data, ConverterCulture=en-US}”
Margin=”24,0,0,12”/>

[/code]

The ConverterParameter can be any custom data for the converter class to act upon, much like the Tag property on elements. ConverterCulture can be set to an Internet Engineering Task Force (IETF) language tag (such as en-US or ko-KR), and the converter receives the appropriate CultureInfo object. In DateConverter, the ToString methods already respect the current culture, so there’s no need to do anything custom with the culture.

Value converters are the key to plugging any kind of custom logic into the data-binding process that goes beyond basic formatting.Whether you want to apply some sort of transformation to the source value before displaying it or change how the target gets updated based on the value of the source, you can easily accomplish this with a class that implements IValueConverter. A very common value converter that people create is a Boolean-to-Visibility converter (usually called BooleanToVisibilityConverter) that can convert between the Visibility enumeration and a Boolean or nullable Boolean. In one direction, true is mapped to Visible, whereas false and null are mapped to Collapsed. In the other direction, Visible is mapped to true, whereas Collapsed is mapped to false.This is useful for toggling the visibility of elements based on the state of an otherwise unrelated element, all in XAML. For example, the following snippet of XAML implements a Show Button check box without requiring any procedural code (other than the value converter):

[code]

<phone:PhoneApplicationPage.Resources>
<local:BooleanToVisibilityConverter x:Key=”BooltoVis”/>
</phone:PhoneApplicationPage.Resources>

<CheckBox x:Name=”CheckBox”>Show Button</CheckBox>

<Button Visibility=”{Binding IsChecked, ElementName=CheckBox,
Converter={StaticResource BoolToVis}}”…/>

[/code]

In this case, the button is visible when (and only when) the check box’s IsChecked property is true.

The Code-Behind

The code-behind for the main page is shown in Listing 21.8.

LISTING 21.8 MainPage.xaml.cs—The Code-Behind for Password & Secrets’Main Page

[code]

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
IApplicationBarMenuItem passwordMenuItem;
public MainPage()
{
InitializeComponent();
this.passwordMenuItem = this.ApplicationBar.MenuItems[0]
as IApplicationBarMenuItem;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// The password menu item is “show password hint” when not logged in,
// or “change password” when logged in
if (CurrentContext.IsLoggedIn)
{
this.passwordMenuItem.Text = “change password”;
// This is only needed when reactivating app and navigating back to this
// page from the details page, because going back can instantiate
// this page in a logged-in state
this.LoginControl.Close();
}
else
{
this.passwordMenuItem.Text = “show password hint”;
}
// Clear the selection so selecting the same item twice in a row will
// still raise the SelectionChanged event
Settings.CurrentNoteIndex.Value = -1;
this.ListBox.SelectedIndex = -1;
if (Settings.NotesList.Value.Count == 0)
this.NoItemsTextBlock.Visibility = Visibility.Visible;
else
this.NoItemsTextBlock.Visibility = Visibility.Collapsed;
}
void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListBox.SelectedIndex >= 0)
{
// Navigate to the details page for the selected item
Settings.CurrentNoteIndex.Value = ListBox.SelectedIndex;
this.NavigationService.Navigate(new Uri(“/DetailsPage.xaml”,
UriKind.Relative));
}
}
void LoginControl_Closed(object sender, EventArgs e)
{
// Now that we’re logged-in, add the “new” button to the application bar
ApplicationBarIconButton newButton = new ApplicationBarIconButton
{
Text = “new”,
IconUri = new Uri(“/Shared/Images/appbar.add.png”, UriKind.Relative)
};
newButton.Click += NewButton_Click;
this.ApplicationBar.Buttons.Add(newButton);
// The password menu item is “show password hint” when not logged in,
// or “change password” when logged in
this.passwordMenuItem.Text = “change password”;
// Now bind the notes list as the data source for the list box,
// because its contents can be decrypted
this.DataContext = Settings.NotesList.Value;
}
// Application bar handlers
void NewButton_Click(object sender, EventArgs e)
{
// Create a new note and add it to the top of the list
Note note = new Note();
note.Modified = DateTimeOffset.Now;
note.ScreenColor = Settings.ScreenColor.Value;
note.TextColor = Settings.TextColor.Value;
note.TextSize = Settings.TextSize.Value;
Settings.NotesList.Value.Insert(0, note);
// “Select” the new note
Settings.CurrentNoteIndex.Value = 0;
// Navigate to the details page for the newly created note
this.NavigationService.Navigate(new Uri(“/DetailsPage.xaml”,
UriKind.Relative));
}
void PasswordMenuItem_Click(object sender, EventArgs e)
{
if (CurrentContext.IsLoggedIn)
{
// Change password
this.NavigationService.Navigate(new Uri(“/ChangePasswordPage.xaml”,
UriKind.Relative));
}
else
{
// Show password hint
if (Settings.PasswordHint.Value == null ||
Settings.PasswordHint.Value.Trim().Length == 0)
{
MessageBox.Show(“Sorry, but there is no hint!”, “Password hint”,
MessageBoxButton.OK);
}
else
{
MessageBox.Show(Settings.PasswordHint.Value, “Password hint”,
MessageBoxButton.OK);
}
}
}
void InstructionsMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/InstructionsPage.xaml”,
UriKind.Relative));
}
void AboutMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(
new Uri(“/Shared/About/AboutPage.xaml?appName=Passwords %26 Secrets”,
UriKind.Relative));
}
}
}

[/code]

FIGURE 21.3 The expanded application bar menu shows “change password” when the user is logged in.

The INotifyPropertyChanged Interface

Although the observable collection takes care off additions and deletions being reflected in the list box, each Note item must provide notifications to ensure that item-specific property changes are reflected in the databound list box. Note does this by implementing INotifyPropertyChanged, as shown in Listing 21.9.

LISTING 21.9 Note.cs—The Note Class Representing Each Item in the List

[code]

using System;
using System.ComponentModel;
using System.Windows.Media;
namespace WindowsPhoneApp
{
public class Note : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// A helper method used by the properties
void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
string encryptedContent;
public string EncryptedContent
{
get { return this.encryptedContent; }
set { this.encryptedContent = value;
OnPropertyChanged(“EncryptedContent”); OnPropertyChanged(“Title”); }
}
DateTimeOffset modified;
public DateTimeOffset Modified
{
get { return this.modified; }
set { this.modified = value; OnPropertyChanged(“Modified”); }
}
int textSize;
public int TextSize
{
get { return this.textSize; }
set { this.textSize = value; OnPropertyChanged(“TextSize”); }
}
Color screenColor;
public Color ScreenColor
{
get { return this.screenColor; }
set { this.screenColor = value;
OnPropertyChanged(“ScreenColor”); OnPropertyChanged(“ScreenBrush”); }
}
Color textColor;
public Color TextColor
{
get { return this.textColor; }
set { this.textColor = value;
OnPropertyChanged(“TextColor”); OnPropertyChanged(“TextBrush”); }
}
// Three readonly properties whose value is computed from other properties:
public Brush ScreenBrush
{
get { return new SolidColorBrush(this.ScreenColor); }
}
public Brush TextBrush
{
get { return new SolidColorBrush(this.TextColor); }
}
public string Title
{
get
{
// Grab the note’s content
string title =
Crypto.Decrypt(this.EncryptedContent, CurrentContext.Password) ?? “”;
// Don’t include more than the first 100 characters, which should be long
// enough, even in landscape with a small font
if (title.Length > 100)
title = title.Substring(0, 100);
// Fold the remaining content into a single line. We can’t use
// Environment.NewLine because it’s rn, whereas newlines inserted from
// a text box are just r
return title.Replace(‘r’, ‘ ‘);
}
}
}
}

[/code]

INotifyCollectionChanged

Observable collections perform their magic by implementing INotifyCollectionChanged, an interface that is very similar to INotifyPropertyChanged.This interface contains a single CollectionChanged event. It is very rare,however, for people to write their own collection class and implement INotifyCollectionChanged rather than simply using the ObservableCollection class.

The Details Page

The details page, shown in Figure 21.4, appears when the user taps a note in the list box on the main page. This page displays the entire contents of the note and enables the user to edit it, delete it, or email its contents. It also provides access to a per-note settings page that gives control over the note’s colors and text size. Listing 21.10 contains this page’s XAML.

FIGURE 21.4 The details page, shown for a white-on-red note.

LISTING 21.10 DetailsPage.xaml—The User Interface for Passwords & Secrets’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 three buttons –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible=”False”>
<shell:ApplicationBarIconButton Text=”delete”
IconUri=”/Shared/Images/appbar.delete.png”
Click=”DeleteButton_Click”/>
<shell:ApplicationBarIconButton Text=”email”
IconUri=”/Shared/Images/appbar.email.png”
Click=”EmailButton_Click”/>
<shell:ApplicationBarIconButton Text=”settings”
IconUri=”/Shared/Images/appbar.settings.png”
Click=”SettingsButton_Click”/>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<phone:PhoneApplicationPage.Resources>
<!– A copy of the text box default style with its border removed and
background applied differently. Compare with the style in Program Files
Microsoft SDKsWindows Phonev7.0DesignSystem.Windows.xaml –>

</phone:PhoneApplicationPage.Resources>
<ScrollViewer>
<Grid>
<!– The full-screen text box –>
<TextBox x:Name=”TextBox” InputScope=”Text”
Style=”{StaticResource PhoneTextBox}”
AcceptsReturn=”True” TextWrapping=”Wrap”
GotFocus=”TextBox_GotFocus” LostFocus=”TextBox_LostFocus”/>
<!– The user control –>
<local:LoginControl x:Name=”LoginControl” Closed=”LoginControl_Closed”/>
</Grid>
</ScrollViewer>
</phone:PhoneApplicationPage>

[/code]

The text box that basically occupies the whole screen is given a custom style that removes its border and ensures the desired background color remains visible whether the text box has focus. The style was created by copying the default style from %ProgramFiles%Microsoft SDKsWindows Phonev7.0DesignSystem.Windows.xaml then making a few tweaks.

Listing 21.11 contains the code-behind for this page.

LISTING 21.11 DetailsPage.xaml.cs—The Code-Behind for Passwords & Secrets’Details Page

[code]

using System;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Tasks;
namespace WindowsPhoneApp
{
public partial class DetailsPage : PhoneApplicationPage
{
bool navigatingFrom;
string initialText = “”;
public DetailsPage()
{
InitializeComponent();
this.Loaded += DetailsPage_Loaded;
}
void DetailsPage_Loaded(object sender, RoutedEventArgs e)
{
if (CurrentContext.IsLoggedIn)
{
// Automatically show the keyboard for new notes.
// This also gets called when navigating away, hence the extra check
// to make sure we’re only doing this when navigating to the page
if (this.TextBox.Text.Length == 0 && !this.navigatingFrom)
this.TextBox.Focus();
}
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
this.navigatingFrom = true;
base.OnNavigatedFrom(e);
if (this.initialText != this.TextBox.Text)
{
// Automatically save the new content
Note n = Settings.NotesList.Value[Settings.CurrentNoteIndex.Value];
n.EncryptedContent =
Crypto.Encrypt(this.TextBox.Text, CurrentContext.Password) ?? “”;
n.Modified = DateTimeOffset.Now;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (CurrentContext.IsLoggedIn)
this.LoginControl.Close();
}
void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
this.ApplicationBar.IsVisible = false;
}
void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
this.ApplicationBar.IsVisible = true;
}
void LoginControl_Closed(object sender, EventArgs e)
{
this.ApplicationBar.IsVisible = true;
// Show the note’s contents
Note n = Settings.NotesList.Value[Settings.CurrentNoteIndex.Value];
if (n != null)
{
this.TextBox.Background = n.ScreenBrush;
this.TextBox.Foreground = n.TextBrush;
this.TextBox.FontSize = n.TextSize;
this.initialText = this.TextBox.Text =
Crypto.Decrypt(n.EncryptedContent, CurrentContext.Password) ?? “”;
}
}
// Application bar handlers:
void DeleteButton_Click(object sender, EventArgs e)
{
if (MessageBox.Show(“Are you sure you want to delete this note?”,
“Delete note?”, MessageBoxButton.OKCancel) == MessageBoxResult.OK)
{
Settings.NotesList.Value.Remove(
Settings.NotesList.Value[Settings.CurrentNoteIndex.Value]);
if (this.NavigationService.CanGoBack)
this.NavigationService.GoBack();
}
}
void EmailButton_Click(object sender, EventArgs e)
{
EmailComposeTask launcher = new EmailComposeTask();
launcher.Body = this.TextBox.Text;
launcher.Subject = “Note”;
launcher.Show();
}
void SettingsButton_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/SettingsPage.xaml”,
UriKind.Relative));
}
}
}

[/code]

A page’s Loaded event is incorrectly raised when navigating away!

This is simply a bug in the current version of Windows Phone.To avoid performance problems, potential flickering, or other issues, consider setting a flag in OnNavigatedFrom that you can check inside Loaded, as done in Listing 21.11.That way, you can be sure that your page-loading logic only runs when the page is actually loading.

The Finished Product

Exit mobile version