Blograby

Notepad (Reading & Writing Files)

The Notepad app enables fast, efficient note-taking. It boasts the following features:

Does this sound familiar? It should, because to a user this app behaves exactly like the preceding chapter’s Passwords & Secrets app, but without the master password and associated encryption. There is one important difference in its implementation, however, that makes it interesting for this chapter. Because the notes stored by Notepad are expected to be longer than the notes stored by Passwords & Secrets, each note is persisted as a separate file in isolated storage. This enables the app to load a note’s contents on-demand rather than loading everything each time the app launches/activates (which is what happens with application settings).

The Main Page

Notepad’s main page works just like the previous chapter’s main page, but without the LoginControl user control and associated logic. Listing 22.1 contains the XAML for the main page, with differences emphasized.

LISTING 22.1 MainPage.xaml—The User Interface for Notepad’s Main Page

[code]

<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.MainPage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”
xmlns: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 one button and one menu item –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar>
<shell:ApplicationBarIconButton Text=”new”
IconUri=”/Shared/Images/appbar.add.png” Click=”NewButton_Click”/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text=”about” Click=”AboutMenuItem_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=”NOTEPAD” 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>
</Grid>
</phone:PhoneApplicationPage>

[/code]

The application bar now has the “new” button from the start because there is no mode in which adding new notes is forbidden.

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

LISTING 22.2 MainPage.xaml.cs—The Code-Behind for Notepad’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
{
public MainPage()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Bind the notes list as the data source for the list box
if (this.DataContext == null)
this.DataContext = Settings.NotesList.Value;
// 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)
NoItemsTextBlock.Visibility = Visibility.Visible;
else
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));
}
}
// 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.Filename = Guid.NewGuid().ToString();
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 AboutMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(
“/Shared/About/AboutPage.xaml?appName=Notepad”, UriKind.Relative));
}
}
}

[/code]

The Details Page

The details page, just like in Passwords & Secrets, displays the entire contents of the note and enables the user to edit it, delete it, change its settings, or email its contents. The page’s XAML is identical to DetailsPage.xaml in the preceding chapter, except the application bar is not marked with IsVisible=”False” because it doesn’t need to be hidden while the LoginControl is shown. Listing 22.3 contains the code-behind for this page with differences emphasized.

LISTING 22.3 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)
{
// 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.SaveContent(this.TextBox.Text);
// Update the title now, so each one can be accessed
// later without reading the file’s contents
string title = this.TextBox.Text.TrimStart();
// 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
n.Title = title.Replace(‘r’, ‘ ‘);
n.Modified = DateTimeOffset.Now;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Show the note’s contents
Note n = Settings.NotesList.Value[Settings.CurrentNoteIndex.Value];
if (n != null)
{
this.initialText = this.TextBox.Text = n.GetContent();
this.TextBox.Background = n.ScreenBrush;
this.TextBox.Foreground = n.TextBrush;
this.TextBox.FontSize = n.TextSize;
}
}
void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
this.ApplicationBar.IsVisible = false;
}
void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
this.ApplicationBar.IsVisible = true;
}
// 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)
{
Note n = Settings.NotesList.Value[Settings.CurrentNoteIndex.Value];
n.DeleteContent();
Settings.NotesList.Value.Remove(n);
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]

The Note Class

Listing 22.4 shows the implementation of the modified Note class used by this app, with differences from the preceding chapter emphasized.

LISTING 22.4 Note.cs—The Code-Behind for Passwords & Secrets’Details Page

[code]

using System;
using System.ComponentModel;
using System.IO;
using System.IO.IsolatedStorage;
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));
}
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); }
}
string title;
public string Title
{
get { return this.title; }
set { this.title = value; OnPropertyChanged(“Title”); }
}
public string Filename { get; set; }
public void SaveContent(string content)
{
using (IsolatedStorageFile userStore =
IsolatedStorageFile.GetUserStoreForApplication())
using (IsolatedStorageFileStream stream =
userStore.CreateFile(this.Filename))
using (StreamWriter writer = new StreamWriter(stream))
{
writer.Write(content);
}
}
public string GetContent()
{
using (IsolatedStorageFile userStore =
IsolatedStorageFile.GetUserStoreForApplication())
{
if (!userStore.FileExists(this.Filename))
return “”;
else
{
using (IsolatedStorageFileStream stream =
userStore.OpenFile(this.Filename, FileMode.Open))
using (StreamReader reader = new StreamReader(stream))
return reader.ReadToEnd();
}
}
}
public void DeleteContent()
{
using (IsolatedStorageFile userStore =
IsolatedStorageFile.GetUserStoreForApplication())
userStore.DeleteFile(this.Filename);
}
}
}

[/code]

When managing files, it’s tempting to simply use the IsolatedStorageFile. GetFileNames method to enumerate and perhaps display the files.This approach has problems, however. For example:

The Settings Page

The settings page, shown in Figure 22.1, enables the customization of any note’s foreground color, background color, and text size. Although these settings are only applied to the current note (stored as properties on the Note instance), the user can check a check box to automatically apply the chosen settings to any new notes created in the future. Listing 22.5 contains the XAML for this page, and Listing 22.6 contains the code-behind.

FIGURE 22.1 The settings page exposes per-note settings and enables you to apply them to all future notes.

LISTING 22.5 SettingsPage.xaml—The User Interface for Notepad’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 settings header –>
<StackPanel Grid.Row=”0” Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock Text=”SETTINGS” Style=”{StaticResource PhoneTextTitle0Style}”/>
<TextBlock Text=”notepad” Style=”{StaticResource PhoneTextTitle1Style}”/>
</StackPanel>
<ScrollViewer Grid.Row=”1”>
<Grid Margin=”{StaticResource PhoneMargin}”>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CheckBox x:Name=”MakeDefaultCheckBox” Grid.ColumnSpan=”2”
Content=”Make these the default settings” Margin=”0,-4,0,0”
Checked=”MakeDefaultCheckBox_IsCheckedChanged”
Unchecked=”MakeDefaultCheckBox_IsCheckedChanged”
local:Tilt.IsEnabled=”True”/>
<!– The two colors –>
<TextBlock Grid.Row=”1” Text=”Screen color”
Foreground=”{StaticResource PhoneSubtleBrush}” Margin=”12,8”/>
<Rectangle Grid.Row=”2” x:Name=”ScreenColorRectangle”
Margin=”{StaticResource PhoneHorizontalMargin}” Height=”90”
Stroke=”{StaticResource PhoneForegroundBrush}”
StrokeThickness=”3” local:Tilt.IsEnabled=”True”
MouseLeftButtonUp=”ScreenColorRectangle_MouseLeftButtonUp”/>
<TextBlock Grid.Row=”1” Grid.Column=”1” Text=”Text color”
Foreground=”{StaticResource PhoneSubtleBrush}” Margin=”12,8”/>
<Rectangle Grid.Row=”2” Grid.Column=”1” x:Name=”TextColorRectangle”
Height=”90” StrokeThickness=”3” local:Tilt.IsEnabled=”True”
Margin=”{StaticResource PhoneHorizontalMargin}”
Stroke=”{StaticResource PhoneForegroundBrush}”
MouseLeftButtonUp=”TextColorRectangle_MouseLeftButtonUp”/>
<!– Text size –>
<TextBlock Grid.Row=”3” Grid.ColumnSpan=”2” Text=”Text size”
Foreground=”{StaticResource PhoneSubtleBrush}”
Margin=”12,20,12,-14”/>
<Grid Grid.Row=”4” Grid.ColumnSpan=”2”>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width=”Auto”/>
</Grid.ColumnDefinitions>
<Slider x:Name=”TextSizeSlider” Minimum=”12” Maximum=”100”
ValueChanged=”TextSizeSlider_ValueChanged”/>
<Button x:Name=”ResetButton” Grid.Column=”1” Content=”reset”
VerticalAlignment=”Top” Click=”ResetButton_Click”
local:Tilt.IsEnabled=”True”/>
</Grid>
<!– Sample text –>
<Rectangle x:Name=”SampleBackground” Grid.Row=”5” Grid.ColumnSpan=”2”
Margin=”-12,0,-12,-12”/>
<TextBlock x:Name=”SampleTextBlock” Grid.Row=”5” Grid.ColumnSpan=”2”
Text=”Sample text.” Padding=”12”/>
</Grid>
</ScrollViewer>
</Grid>
</phone:PhoneApplicationPage>

[/code]

LISTING 22.6 SettingsPage.xaml.cs—The Code-Behind for Notepad’s Settings Page

[code]

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class SettingsPage : PhoneApplicationPage
{
public SettingsPage()
{
InitializeComponent();
}
protected override void OnBackKeyPress(CancelEventArgs e)
{
base.OnBackKeyPress(e);
// Doing this here instead of OnNavigatedFrom, so it’s not
// applied when navigating forward to color picker pages
if (Settings.MakeDefault.Value)
{
// Apply everything as defaults, too
Note n = Settings.NotesList.Value[Settings.CurrentNoteIndex.Value];
Settings.ScreenColor.Value = n.ScreenColor;
Settings.TextColor.Value = n.TextColor;
Settings.TextSize.Value = n.TextSize;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Note n = Settings.NotesList.Value[Settings.CurrentNoteIndex.Value];
// Apply any color just selected from the color picker
if (Settings.TempScreenColor.Value != null)
{
n.ScreenColor = Settings.TempScreenColor.Value.Value;
Settings.TempScreenColor.Value = null;
}
if (Settings.TempTextColor.Value != null)
{
n.TextColor = Settings.TempTextColor.Value.Value;
Settings.TempTextColor.Value = null;
}
// Respect the saved settings
this.MakeDefaultCheckBox.IsChecked = Settings.MakeDefault.Value;
this.ScreenColorRectangle.Fill = new SolidColorBrush(n.ScreenColor);
this.TextColorRectangle.Fill = new SolidColorBrush(n.TextColor);
this.SampleBackground.Fill = this.ScreenColorRectangle.Fill;
this.SampleTextBlock.Foreground = this.TextColorRectangle.Fill;
this.TextSizeSlider.Value = n.TextSize;
}
void ScreenColorRectangle_MouseLeftButtonUp(object sender,
MouseButtonEventArgs e)
{
// Get a string representation of the colors we need to pass to the color
// picker, without the leading #
string currentColorString = Settings.NotesList.Value
[Settings.CurrentNoteIndex.Value].ScreenColor.ToString().Substring(1);
string defaultColorString =
Settings.ScreenColor.Value.ToString().Substring(1);
// The color picker works with the same isolated storage value that the
// Setting works with, but we have to clear its cached value to pick up
// the value chosen in the color picker
Settings.TempScreenColor.ForceRefresh();
// Navigate to the color picker
this.NavigationService.Navigate(new Uri(
“/Shared/Color Picker/ColorPickerPage.xaml?”
+ “&currentColor=” + currentColorString
+ “&defaultColor=” + defaultColorString
+ “&settingName=TempScreenColor”, UriKind.Relative));
}
void TextColorRectangle_MouseLeftButtonUp(object sender,
MouseButtonEventArgs e)
{
// Get a string representation of the colors, without the leading #
string currentColorString = Settings.NotesList.Value
[Settings.CurrentNoteIndex.Value].TextColor.ToString().Substring(1);
string defaultColorString =
Settings.TextColor.Value.ToString().Substring(1);
// The color picker works with the same isolated storage value that the
// Setting works with, but we have to clear its cached value to pick up
// the value chosen in the color picker
Settings.TempTextColor.ForceRefresh();
// Navigate to the color picker
this.NavigationService.Navigate(new Uri(
“/Shared/Color Picker/ColorPickerPage.xaml?”
+ “showOpacity=false”
+ “&currentColor=” + currentColorString
+ “&defaultColor=” + defaultColorString
+ “&settingName=TempTextColor”, UriKind.Relative));
}
void TextSizeSlider_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e)
{
// Gets called during InitializeComponent
if (this.TextSizeSlider != null)
{
int textSize = (int)Math.Round(this.TextSizeSlider.Value);
Settings.NotesList.Value[Settings.CurrentNoteIndex.Value].TextSize =
textSize;
this.SampleTextBlock.FontSize = textSize;
}
}
void MakeDefaultCheckBox_IsCheckedChanged(object sender, RoutedEventArgs e)
{
Settings.MakeDefault.Value = this.MakeDefaultCheckBox.IsChecked.Value;
}
void ResetButton_Click(object sender, RoutedEventArgs e)
{
int textSize = Settings.TextSize.DefaultValue;
this.TextSizeSlider.Value = textSize;
Settings.NotesList.Value[Settings.CurrentNoteIndex.Value].TextSize =
textSize;
this.SampleTextBlock.FontSize = textSize;
}
}
}

[/code]

To work with the color picker that writes directly to a key in the isolated storage application settings, TempScreenColor and TempTextColor settings are used. These values are then applied to the current note’s properties inside OnNavigatedTo.

The Finished Product

Exit mobile version