Share via


Exercise 1: Introduction to the Windows Phone Choosers

A chooser is an API that launches one of the built-in applications through which a user completes a task and then returns some kind of data to the calling application. For example, the phone chooser launches the "contact people" experience, enabling a search for a particular contact. When successful, the requested contact information is returned. Another example of this is the PhotoChooserTask. An application can use this chooser to show the Photo Chooser application to allow the user to select a photo. The user can always cancel out of the Photo Chooser instead. Once the chooser is dismissed, the calling application is activated and supplied with the Chooser’s results. The result includes a value that indicates whether the user completed the task and, if the user did complete the task, the result includes additional relevant data, such as an IO stream containing the selected photo’s image data.

Task 1 – Using Choosers

  1. Open the starter solution from this lab’s Source\Begin folder.

    Note:
    Alternatively, you can choose to continue working on the solution created in the previous exercise.

  2. Open MakePhoneCallPage.xaml.cs.
  3. Note we overrode the OnNavigatedFrom and OnNavigatedTo functions in order to add debug information when the application is being tombstoned.

    In this page, a phone call is made to one of the contacts added during the previous exercise. To get a contact’s phone number, this page uses the PhoneNumberChooserTask class. This class enables an application to launch the Contacts application to obtain the phone number of a contact selected by the user. The chooser works asynchronously, therefore the “Completed” event must be subscribed to before launching the Chooser. After the chooser returns a phone number, this page uses PhoneCallTask which makes a phone call to the selected phone number.

  4. Start by adding the following variable to the class:

    C#

    PhoneNumberChooserTask phoneNumberChooserTask;

  5. Add the following bold-yellow-highlighted code to the class’s constructor after the InitializeComponent method call:

    C#

    public MakePhoneCallPage() { InitializeComponent(); Debug.WriteLine("***\t In constructor of MakePhoneCallPage\t ***"); phoneNumberChooserTask = new PhoneNumberChooserTask(); phoneNumberChooserTask.Completed += new EventHandler<PhoneNumberResult>(phoneNumberChooserTask_Completed); }

  6. Add the phoneNumberChooserTask_Completed event handler function to manage the completed event using the following code:

    C#

    void phoneNumberChooserTask_Completed(object sender, PhoneNumberResult e) { string debugMsg = string.Format( "***\t In phoneNumberChooserTask_Completed function of MakePhoneCallPage, phone number returned: {0}, for contact: {1}\t ***", e.PhoneNumber, e.DisplayName); Debug.WriteLine(debugMsg); if (e.TaskResult == TaskResult.OK) { PhoneCallTask phoneCallTask = new PhoneCallTask(); phoneCallTask.PhoneNumber = e.PhoneNumber; phoneCallTask.Show(); } else if (e.TaskResult == TaskResult.Cancel) MessageBox.Show("Cannot make a phone call without a phone number", "Number not selected", MessageBoxButton.OK); else MessageBox.Show("Error getting phone number:\n" + e.Error.Message, "Fail", MessageBoxButton.OK); }

  7. Add the following code to the btnMakePhoneCall_Click handler function:

    C#

    private void btnMakePhoneCall_Click(object sender, RoutedEventArgs e) { phoneNumberChooserTask.Show(); }

  8. Press F5 to compile and run the application.
  9. Navigate to the “Make a Phone Call” page and click Make Call:
  10. Select the contact:

    Figure 1

    Selecting a contact

  11. If there are multiple phone numbers for a single contact, an additional phone selection step is displayed. In this case, select one of the contact’s phone numbers. If there is only one phone number, the contact selection serves as the phone selection.
  12. Click call to make the phone call:

    Figure 2

    Making a Call

  13. Click end call to return to MakePhoneCallPage:
  14. Press SHIFT+F5 to stop the debugging and return to the Visual Studio.
  15. Open the SendSMSPage.xaml.cs.
  16. Note we overrode the OnNavigatedFrom and OnNavigatedTo functions in order to add debug information when the application is being tombstoned.

    This page allows us to send an SMS message to one of the contacts added during the previous exercise. Like the previous page we added, this page uses the PhoneNumberChooserTask class to pick a contact’s phone number. After the chooser returns a phone number, this page uses SmsComposeTask, which enables composing SMS messages and sending them to the selected phone number.

  17. Start by adding the following variable to the class:

    C#

    PhoneNumberChooserTask phoneNumberChooserTask;

  18. Add the following bold-yellow-highlighted code to the class’s constructor after the InitializeComponent method call:

    C#

    public UsePhoneNumberPage() { InitializeComponent(); Debug.WriteLine("***\t In constructor of SendSMSPage\t ***"); phoneNumberChooserTask = new PhoneNumberChooserTask(); phoneNumberChooserTask.Completed += new EventHandler<PhoneNumberResult>(phoneNumberChooserTask_Completed); }

  19. Add the phoneNumberChooserTask_Completed event handler function to manage the completed event using the following code:

    C#

    void phoneNumberChooserTask_Completed(object sender, PhoneNumberResult e) { string debugMsg = string.Format( "***\t In phoneNumberChooserTask_Completed function of SendSMSPage, phone number returned: {0}, for contact: {1}\t ***", e.PhoneNumber, e.DisplayName); Debug.WriteLine(debugMsg); if (e.TaskResult == TaskResult.OK) { SmsComposeTask smsComposeTask = new SmsComposeTask(); if(!string.IsNullOrEmpty(txtInput.Text)) smsComposeTask.Body = txtInput.Text; smsComposeTask.To = e.PhoneNumber; smsComposeTask.Show(); } else if (e.TaskResult == TaskResult.Cancel) MessageBox.Show("Cannot send SMS without a phone number", "Number not selected", MessageBoxButton.OK); else MessageBox.Show("Error getting phone number:\n" + e.Error.Message, "Fail", MessageBoxButton.OK); }

  20. Add the following code to the btnSendSMS_Click handler function:

    C#

    private void btnSendSMS_Click(object sender, RoutedEventArgs e) { phoneNumberChooserTask.Show(); }

  21. Press F5 to compile and run the application.
  22. Navigate to the “Send SMS” page, enter a message and click Send SMS:

    Figure 3

    Sending the SMS message

  23. Select the contact.
  24. If there are multiple phone numbers for a single contact, an additional phone selection step is displayed. In this case, select one of the contact’s phone numbers. If there is only one phone number, the contact selection serves as the phone selection.
  25. Modify the message entered previously, if needed, and click Send ():

    Figure 4

    Sending the SMS message

  26. Press SHIFT+F5 to stop the debugging and return to the Visual Studio.

    Note:
    Note: The following steps demonstrate sending an email to a contact. Remember, as we have previously stated, that this functionality is unavailable when using the Windows® Phone Emulator.

  27. Open the UseEmailAddressPage.xaml.cs.
  28. Note we overrode the OnNavigatedFrom and OnNavigatedTo functions in order to add debug information when the application is being tombstoned.

    In this page an email message is sent to one of the contacts added during the previous exercise. To get a contact’s email address, this page uses the EmailAddressChooserTask class. This class enables an application to launch the Contacts application to obtain an email address for a contact selected by the user. The chooser works asynchronously, therefore the “Completed” event must be subscribed to before launching the Chooser. After the chooser returns an email address, this page uses the EmailComposeTask which we have seen in the previous exercise.

  29. Press F5 to compile and run the application.
  30. Navigate to the “Use Contact’s Email Address” page and click Send Email:

    Figure 5

    Sending the Email message

  31. Select the contact.
  32. If there are multiple email addresses for a single contact, an additional selection step is displayed. In this case, select one of the contact’s addresses.
  33. Compose an email message (or keep the pre-populated email text) and click Send.
  34. Press SHIFT+F5 to stop the debugging and return to the Visual Studio.
  35. Open ChoosePhotoPage.xaml.cs.
  36. Add the following using statements to the class:

    C#

    using System.Windows.Media.Imaging;

  37. Note we overrode the OnNavigatedFrom and OnNavigatedTo functions in order to add debug information when the application is being tombstoned.

    In this page a photo is chosen from the Image Hub. To choose the photo, this page uses the PhotoChooserTask class. This class allows an application to launch an image selector that displays images from the Image Hub. The chooser works asynchronously, therefore the “Completed” event must be subscribed to before launching the chooser. After the chooser returns an image stream, this page displays it.

  38. Start by adding the following variable to the class:

    C#

    PhotoChooserTask photoChooserTask;

  39. Add the following bold-yellow-highlighted code to the class’s constructor after the InitializeComponent method call:

    C#

    public ChoosePhotoPage() { InitializeComponent(); Debug.WriteLine("***\t In constructor of ChoosePhotoPage\t ***"); photoChooserTask = new PhotoChooserTask(); photoChooserTask.Completed += new EventHandler<PhotoResult>(photoChooserTask_Completed); }

  40. Add the photoChooserTask_Completed event handler function to manage the completed event using the following code:

    C#

    void photoChooserTask_Completed(object sender, PhotoResult e) { Debug.WriteLine("***\t In photoChooserTask_Completed function of ChoosePhotoPage\t ***"); if (e.TaskResult == TaskResult.OK) { BitmapImage bitmap = new BitmapImage(); bitmap.SetSource(e.ChosenPhoto); imgChosenPhoto.Source = bitmap; } else if (e.TaskResult == TaskResult.Cancel) MessageBox.Show("No photo was chosen - operation was cancelled", "Photo not chosen", MessageBoxButton.OK); else MessageBox.Show("Error while choosing photo:\n" + e.Error.Message, "Fail", MessageBoxButton.OK); }

  41. Add the following code to the btnChoosePhoto_Click handler function:

    C#

    private void btnChoosePhoto_Click(object sender, RoutedEventArgs e) { photoChooserTask.Show(); }

  42. Press F5 to compile and run the application.
  43. Navigate to the “Choose a Photo” page and click Choose a Photo:
  44. Choose a photo from the Image Hub by clicking it.

    Figure 6

    Choosing an image from the Image Hub

  45. The chosen photo should now appear in the ChoosePhotoPage.

    Figure 7

    Chosen photo appears in the ChoosePhotoPage

  46. Press SHIFT+F5 to stop the debugging and return to the Visual Studio.
  47. Open the TakePicturePage.xaml.cs.
  48. Add the following using statements to the class:

    C#

    using System.Windows.Media.Imaging; using System.IO.IsolatedStorage; using System.IO; using Microsoft.Xna.Framework.Media;

  49. Note we overrode the OnNavigatedFrom and OnNavigatedTo functions in order to add debug information when the application is being tombstoned.

    In this page, a new picture is captured and saved on the phone. To capture the picture, this page uses the CameraCaptureTask class. This class allows an application to launch the Camera application and capture a picture using the phone's camera. The chooser works asynchronously, therefore the “Completed” event must be subscribed to before launching the chooser. After the chooser returns a picture stream, this page saves it to the application's isolated storage as well as to the Image Hub.

  50. Start by adding the following variable to the class:

    C#

    CameraCaptureTask cameraCaptureTask;

  51. Add the following Bold-yellow-highlighted code to the class’s constructor after the InitializeComponent method call:

    C#

    public TakePicturePage() { InitializeComponent(); Debug.WriteLine("***\t In constructor of TakePicturePage\t ***"); cameraCaptureTask = new CameraCaptureTask(); cameraCaptureTask. Completed += new EventHandler<PhotoResult>(cameraCaptureTask_Completed); }

  52. Add the cameraCaptureTask_Completed event handler function to manage the completed event using the following code:

    C#

    void cameraCaptureTask_Completed(object sender, PhotoResult e) { Debug.WriteLine("***\t In cameraCaptureTask_Completed function of TakePicturePage\t ***"); if (e.TaskResult == TaskResult.OK) { BitmapImage bitmap = new BitmapImage(); bitmap.SetSource(e.ChosenPhoto); imgTakenPicture.Source = bitmap; chkAddToImageHub.IsEnabled = true; btnSavePicture.IsEnabled = true; } else if (e.TaskResult == TaskResult.Cancel) MessageBox.Show("Photo was not captured - operation was cancelled", "Photo not captured", MessageBoxButton.OK); else MessageBox.Show("Error while capturing photo:\n" + e.Error.Message, "Fail", MessageBoxButton.OK); }

  53. Add the following code to the btnTakePicture_Click handler function:

    C#

    private void btnTakePicture_Click(object sender, RoutedEventArgs e) { cameraCaptureTask.Show(); }
    Note:
    Remember that multimedia tasks do not work while the device is connected to a computer.

  54. Add the following code to the btnSavePicture_Click handler function:

    C#

    private void btnSavePicture_Click(object sender, RoutedEventArgs e) { BitmapImage bitmap = (BitmapImage)imgTakenPicture.Source; string photosStr = "Photo"; using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication()) { const string fileName = "image.jpg"; if (storage.FileExists(fileName)) { storage.DeleteFile(fileName); } using (IsolatedStorageFileStream imageFileStream = storage.CreateFile(fileName)) { WriteableBitmap writableBitmap = new WriteableBitmap(bitmap); writableBitmap.SaveJpeg(imageFileStream, writableBitmap.PixelWidth, writableBitmap.PixelHeight, 0, 90); } if (chkAddToImageHub.IsChecked.Value) { using (IsolatedStorageFileStream imageFileStream = storage.OpenFile(fileName, FileMode.Open, FileAccess.Read)) { MediaLibrary library = new MediaLibrary(); library.SavePicture(fileName, imageFileStream); photosStr += 's'; } } } MessageBox.Show(photosStr + " saved successfully", "Success", MessageBoxButton.OK); }
    Note:
    This code sample uses a constant image file name – image.jpg. We suggest that you give a more meaningful file name to an image before saving it to the Image Hub.

  55. Press F5 to compile and run the application.
  56. Navigate to the “Take A Picture” page and click Capture New Picture:
  57. In the Camera page, click :

    Note:
    When running the application on a real Windows Phone device, use the physical capture button.

    The Windows® Phone emulator’s camera task, does not display any image. The emulator is not connected the computer’s camera (if you have one). Therefore you will see a white screen with a black triangle moving along its edges.

    Figure 8

    Camera Task Application

  58. Click Accept, or click Retake to capture another picture.
  59. The captured picture should now appear in the TakePicturePage:

    Figure 9

    Picture captured appear in the TakePicturePage

  60. To have the picture saved to the Image Hub select the Add to Image Hub check box.
  61. Click Save Picture. The image is saved in the application's isolated storage and (optionally) in the Image Hub.
  62. To return to the application, click Back ().
  63. Press SHIFT+F5 to stop the debugging and return to the Visual Studio.
  64. Open the UseAddressPage.xaml.cs file.
  65. Note we overrode the OnNavigatedFrom and OnNavigatedTo functions in order to add debug information when the application is being tombstoned.

    In this page the user may view the address of a selected contact. To get a contact’s address, this page uses the AddressChooserTask class. This class enables an application to launch the Contacts application to obtain an address for a contact selected by the user. The chooser works asynchronously, therefore the “Completed” event must be subscribed to before launching the Chooser. After the chooser returns an address, its contents will be displayed on the page.

  66. Start by adding the following variable to the class:

    C#

    AddressChooserTask addressChooserTask;

  67. Add the following bold-yellow-highlighted code to the class’s constructor after the InitializeComponent method call:

    C#

    public UseAddressPage() { InitializeComponent(); Debug.WriteLine("***\t In constructor of UseEmailAddressPage\t ***"); addressChooserTask = new AddressChooserTask(); addressChooserTask.Completed += new EventHandler<AddressResult>(addressChooserTask_Completed); }

  68. Add the addressChooserTask_Completed event handler function to handle the completed event using the following code:

    C#

    void addressChooserTask_Completed(object sender, AddressResult e) { string debugMsg = string.Format( "***\t In addressChooserTask_Completed function of UseAddressPage, address returned: {0}, for contact: {1}\t ***", e.Address, e.DisplayName); Debug.WriteLine(debugMsg); if (e.TaskResult == TaskResult.OK) { txtDisplayName.Text = e.DisplayName; txtAddress.Text = e.Address; } else if (e.TaskResult == TaskResult.Cancel) MessageBox.Show("Cannot display address information.", "Address not selected", MessageBoxButton.OK); else MessageBox.Show("Error getting address:\n" + e.Error.Message, "Fail", MessageBoxButton.OK); }

  69. Add the following code to the btnGetAddress_Click handler function:

    C#

    private void btnGetAddress_Click(object sender, RoutedEventArgs e) { addressChooserTask.Show(); }

  70. Press F5 to compile and run the application.
  71. Navigate to the “Use Contact’s Address” page and click Get Address:

    Figure 10

    Getting a contact’s address

  72. Select the contact.
  73. If there are multiple addresses for a single contact, an additional selection step is displayed. In this case, select one of the contact’s addresses.
  74. See that the selected address is displayed on the page.
  75. Press SHIFT+F5 to stop the debugging and return to the Visual Studio.

Task 2 – Searching for Contacts and Appointments

New in the Windows® Phone 7.1 is the ability to search for appointments and contacts using the new Appointments and Contacts classes located under Microsoft.Phone.UserData. In this exercise we will add additional pages to demonstrate these new capabilities.

Note:
 This exercise uses controls available in the Silverlight For Windows Phone Toolkit. The lab comes deployed with the toolkit, but for future reference it may be downloaded from: https://silverlight.codeplex.com/
  1. Open the SearchAppointmentsPage.xaml.cs file.
  2. Add the following using statement to the class:

    C#

    using System.Collections.ObjectModel;

  3. Add the following fields and properties to the class:

    C#

    public ObservableCollection<Appointment> SearchResults { get; private set; } Appointments appointments; public Visibility SearchVisibility { get { return (Visibility)GetValue(SearchVisibilityProperty); } set { SetValue(SearchVisibilityProperty, value); } } public static readonly DependencyProperty SearchVisibilityProperty = DependencyProperty.Register("SearchVisibility", typeof(Visibility), typeof(SearchAppointmentsPage), null); public Visibility ResultsVisibility { get { return (Visibility)GetValue(ResultsVisibilityProperty); } set { SetValue(ResultsVisibilityProperty, value); } } public static readonly DependencyProperty ResultsVisibilityProperty = DependencyProperty.Register("ResultsVisibility", typeof(Visibility), typeof(SearchAppointmentsPage), null);

  4. Override the OnNavigatedFrom and OnNavigatedTo functions in order to add debug information when the application is being tombstoned and to remember the search times supplied by the user.

    C#

    protected override void OnNavigatedFrom(NavigationEventArgs e) { Debug.WriteLine("***\t In OnNavigatedFrom function of SearchAppointmentsPage\t ***"); if (e.NavigationMode == NavigationMode.Back) { if (State.ContainsKey("StartTime")) { State.Remove("StartTime"); } if (State.ContainsKey("EndTime")) { State.Remove("EndTime"); } if (State.ContainsKey("ResultsVisible")) { State.Remove("ResultsVisible"); } } else { State["StartTime"] = dateStart.Value.Value.Add(timeStart.Value.Value.TimeOfDay); State["EndTime"] = dateEnd.Value.Value.Add(timeEnd.Value.Value.TimeOfDay); State["ResultsVisible"] = ResultsVisibility; } base.OnNavigatedFrom(e); } protected override void OnNavigatedTo(NavigationEventArgs e) { Debug.WriteLine("***\t In OnNavigatedTo function of SearchAppointmentsPage\t ***"); if (State.Count > 0) { if (e.Uri.OriginalString.EndsWith("DatePickerPage.xaml")) { timeStart.Value = new DateTime().Add( ((DateTime)State["StartTime"]).TimeOfDay); timeEnd.Value = new DateTime().Add( ((DateTime)State["EndTime"]).TimeOfDay); } if (e.Uri.OriginalString.EndsWith("TimePickerPage.xaml")) { dateStart.Value = ((DateTime)State["StartTime"]).Date; dateEnd.Value = ((DateTime)State["EndTime"]).Date; } ResultsVisibility = (Visibility)State["ResultsVisible"]; SearchVisibility = ResultsVisibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible; // Refresh the search if (ResultsVisibility == Visibility.Visible) { btnSearchAppointments_Click(this, null); } } base.OnNavigatedTo(e); }

    In this page the user may search for appointments that take place between two points in time using the Appointments chooser. The chooser works asynchronously, therefore the “SearchCompleted” event must be subscribed to before launching the Chooser. After the chooser returns the relevant appointments, they will be displayed on the page.

  5. Add the following bold-yellow-highlighted code to the class’s constructor after the InitializeComponent method call:

    C#

    public UseAddressPage() { InitializeComponent(); Debug.WriteLine("***\t In constructor of SearchAppointments\t ***"); SearchResults = new ObservableCollection<Appointment>(); ResultsVisibility = Visibility.Collapsed; SearchVisibility = Visibility.Visible; appointments = new Appointments(); appointments.SearchCompleted += new EventHandler<AppointmentsSearchEventArgs>(appointments_SearchCompleted); }

  6. Localize the appointments_SearchCompleted method and replace it with the event handler function and an additional method to handle the completed event using the following code:

    C#

    void appointments_SearchCompleted(object sender, AppointmentsSearchEventArgs e) { Dispatcher.BeginInvoke(() => UpdateResults(e.Results)); } private void UpdateResults(IEnumerable<Appointment> result) { SearchResults.Clear(); foreach (Appointment appointment in result) { SearchResults.Add(appointment); } ResultsVisibility = Visibility.Visible; SearchVisibility = Visibility.Collapsed; }

  7. Also replace the following methods (btnSearchAppointments_Click and btnNewSearch_Click) to create the handlers for the two UI buttons on the page:

    C#

    private void btnSearchAppointments_Click(object sender, RoutedEventArgs e) { if (!dateStart.Value.HasValue || !timeStart.Value.HasValue) { MessageBox.Show("Please supply a start time."); return; } if (!dateEnd.Value.HasValue || !timeEnd.Value.HasValue) { MessageBox.Show("Please supply an end time."); return; } appointments.SearchAsync(dateStart.Value.Value.Add(timeStart.Value.Value.TimeOfDay), dateEnd.Value.Value.Add(timeEnd.Value.Value.TimeOfDay), null); } private void btnNewSearch_Click(object sender, RoutedEventArgs e) { SearchVisibility = Visibility.Visible; ResultsVisibility = Visibility.Collapsed; }

  8. Press F5 to compile and run the application.
  9. Navigate to the “Search Appointments” page and fill out a start and end time between which to look for appointments and press the “Search Appointments” button:

    Figure 11

    Looking for appointments

    Figure 12

    Appointments located

  10. See that the relevant appointments appear on the page.
  11. Press SHIFT+F5 to stop the debugging and return to the Visual Studio.
  12. Open the FindNextAppointmentPage.xaml.cs file.
  13. Add the following fields and properties to the class:

    C#

    Appointments appointments; public Appointment FirstAppointment { get { return (Appointment)GetValue(FirstAppointmentProperty); } set { SetValue(FirstAppointmentProperty, value); } } public static readonly DependencyProperty FirstAppointmentProperty = DependencyProperty.Register("FirstAppointment", typeof(Appointment), typeof(FindNextAppointmentPage), null);

  14. Note we overrode the OnNavigatedFrom and OnNavigatedTo functions in order to add debug information when the application is being tombstoned and to refresh the search results when returning from tombstoning.

    In this page the user may find his nearest appointment using the Appointments chooser. The chooser works asynchronously, therefore the “SearchCompleted” event must be subscribed to before launching the Chooser. After the chooser returns the relevant appointment, it will be displayed on the page.

  15. Add the following bold-yellow-highlighted code to the class’s constructor after the InitializeComponent method call:

    C#

    public UseAddressPage() { InitializeComponent(); Debug.WriteLine("***\t In constructor of FindNextAppointmentPage\t ***"); appointments = new Appointments(); appointments.SearchCompleted += new EventHandler<AppointmentsSearchEventArgs>(appointments_SearchCompleted); }

  16. Replace the following method, appointments_SearchCompleted event handler function and an additional method to handle the completed event using the following code:

    C#

    private void appointments_SearchCompleted(object sender, AppointmentsSearchEventArgs e) { Dispatcher.BeginInvoke(() => UpdateResults(e.Results.FirstOrDefault())); } private void UpdateResults(Appointment result) { if (result == null) { MessageBox.Show("There are no appointments."); return; } FirstAppointment = result; }

  17. Localize the btnFindNext_Click method and replace with following code to create the handler for the UI button on the page:

    C#

    private void btnFindNext_Click(object sender, RoutedEventArgs e) { appointments.SearchAsync(DateTime.Now, DateTime.MaxValue, 1, null); }

  18. Press F5 to compile and run the application.
  19. Navigate to the “Find Next Appointments” page and press the “Find Next Appointment” button:

    Figure 13

    Displaying the next appointment

  20. See that the relevant appointment appears on the page.
  21. Press SHIFT+F5 to stop the debugging and return to the Visual Studio.
  22. Open the ShowContactsPage.xaml.cs file.
  23. Add the following fields and properties to the class:

    C#

    private Contacts contacts; public ObservableCollection<ContactsGroup> ContactsCollection { get { return (ObservableCollection<ContactsGroup>)GetValue(ContactsCollectionProperty); } set { SetValue(ContactsCollectionProperty, value); } } public static readonly DependencyProperty ContactsCollectionProperty = DependencyProperty.Register("ContactsCollection", typeof(ObservableCollection<ContactsGroup>), typeof(ShowContactsPage), null); public Visibility ContactsListVisibility { get { return (Visibility)GetValue(ContactsListVisibilityProperty); } set { SetValue(ContactsListVisibilityProperty, value); } } public static readonly DependencyProperty ContactsListVisibilityProperty = DependencyProperty.Register("ShowContactsList", typeof(Visibility), typeof(ShowContactsPage), null);

  24. Override the OnNavigatedFrom and OnNavigatedTo functions in order to add debug information when the application is being tombstoned and to refresh the search results when returning from tombstoning or coming from the main page.

    C#

    protected override void OnNavigatedFrom(NavigationEventArgs e) { Debug.WriteLine("***\t In OnNavigatedFrom function of ShowContactsPage\t ***"); base.OnNavigatedFrom(e); // Hide the contacts list if we go back to the main page if (e.IsNavigationInitiator && e.NavigationMode == NavigationMode.Back) { ContactsListVisibility = Visibility.Collapsed; ((App)App.Current).ShowContactsPage = null; } } protected override void OnNavigatedTo(NavigationEventArgs e) { Debug.WriteLine("***\t In OnNavigatedTo function of ShowContactsPage\t ***"); base.OnNavigatedTo(e); ((App)App.Current).ShowContactsPage = this; // Refresh the contact list unless it is already visible (we make // sure it is not visible if it needs a // refresh) if (ContactsListVisibility == Visibility.Collapsed) { contacts.SearchAsync(String.Empty, FilterKind.None, null); } }

    In this page the user is shown a list of all contacts by using information retrieved with the Contacts chooser. The chooser works asynchronously, therefore the “SearchCompleted” event must be subscribed to before launching the Chooser. After the chooser returns the relevant contact information, it will be displayed on the page.

  25. Add the following bold-yellow-highlighted code to the class’s constructor after the InitializeComponent method call:

    C#

    public ShowContactsPage() { InitializeComponent(); Debug.WriteLine("***\t In constructor of ShowContactsPage\t ***"); ContactsListVisibility = Visibility.Collapsed; ContactsCollection = new ObservableCollection<ContactsGroup>(); contacts = new Contacts(); contacts.SearchCompleted += contacts_SearchCompleted; }

    The ContactsGroup class is used to group search results before displaying them. We will introduce this class later in the lab.

  26. Add the contacts_SearchCompleted event handler function using the following code:

    C#

    void contacts_SearchCompleted(object sender, ContactsSearchEventArgs e) { // Group all contacts according to the first letter in their display // name var itemsSource = e.Results.GroupBy(c => c.DisplayName.First()). OrderBy(group => group.Key).Select(group => new ContactsGroup( group)); Dispatcher.BeginInvoke(() => { ContactsListVisibility = Visibility.Visible; ContactsCollection = new ObservableCollection<ContactsGroup>(itemsSource); }); }

  27. Localize the ApplicationBarSearchButton_Click method and replace with the following code to handle presses on the application bar button that will lead the user to an additional search screen:

    C#

    private void ApplicationBarSearchButton_Click(object sender, EventArgs e) { NavigationService.Navigate(new Uri("/Views/SearchContactsPage.xaml", UriKind.Relative)); }

  28. Add an additional class in the same file. This class is the ContactsGroup class we have mentioned previously:

    C#

    public class ContactsGroup : IEnumerable<Contact> { private IEnumerable<Contact> items; public string Title { get; set; } /// <summary> /// Creates a new group of contracts. The group's title will be the /// first letter in the first contact's display name. /// </summary> /// <param name="items">The contacts to include in the group. /// </param> public ContactsGroup(IEnumerable<Contact> items) { this.items = items; Title = char.ToLower(items.First().DisplayName.First()). ToString(); } #region IEnumerable<Contact> Members public IEnumerator<Contact> GetEnumerator() { return items.GetEnumerator(); } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { return items.GetEnumerator(); } #endregion }

  29. Open the App.xaml.cs file.
  30. Add the following highlighted code to the Application_Deactivated class:

    C#

    private void Application_Deactivated(
    FakePre-63dfbdbbcbcd41eebdb3cbf66de974e9-e2772b0867984c26ac7d24a77df29b88FakePre-e88ff4efaa174823a8248b0f856cf0c8-e81fdf03944f42b5b61df82032760651FakePre-4097e3556f944321ae0beab2cfdc774e-e5ea4c778e97444682e9a6df2b5e2edcFakePre-e667b40e5d094c2daecd8a25001e5d43-46db2e296d744047b258011734f5354aFakePre-b8e0f6362f674ff8b645490f994155f9-30fcc10514bc469d89671a68ed635bd7FakePre-e504a24f049143f197a2325aba5ed821-8244620c289b4ee29faa3802a166177cFakePre-4026bebd139c47b2959b9e537b5ae8a3-4ff869b84cec496989ce72bdb85211f0FakePre-6f786e9cf4dc4116bf368de6872f1b7c-8f30bd3ee3184a9081d56564e520ca99FakePre-9af8e2665cb74af9bbbe38bd6a05a400-6b56cc84ca2741bc8d6477bd50810e2dFakePre-81f06be614e44a5a9cfbec9a7551ec96-33cb872d90074f35a12dc8f13fd65274

  31. Press F5 to compile and run the application.
  32. Navigate to the “Search Contacts” page and you should see the device’s contacts:

    Figure 14

    Displaying the device’s contacts

    Pressing the button in the application bar will cause the application to crash as we have yet to add the page that it leads to we will add this page in the next steps.

  33. Press SHIFT+F5 to stop the debugging and return to the Visual Studio.
  34. Open the SearchContactsPage.xaml.cs file.
  35. Add the following fields and properties to the class:

    C#

    private object syncObject = new object(); private bool searchInProgress = false; private bool searchQueued = false; Dictionary<string, FilterKind> filterDictionary; private Contacts contacts; public ObservableCollection<Contact> SearchResults { get { return (ObservableCollection<Contact>)GetValue( SearchResultsProperty); } set { SetValue(SearchResultsProperty, value); } } public static readonly DependencyProperty SearchResultsProperty = DependencyProperty.Register("SearchResults", typeof(ObservableCollection<Contact>), typeof(SearchContactsPage), null);

  36. Override the OnNavigatedFrom and OnNavigatedTo functions in order to add debug information when the application is being tombstoned and to refresh the search results when returning from tombstoning or coming from the main page.

    C#

    protected override void OnNavigatedFrom(NavigationEventArgs e) { Debug.WriteLine("***\t In OnNavigatedFrom function of SearchContactsPage\t ***"); if (e.NavigationMode == NavigationMode.Back) { if (State.ContainsKey("SearchTerm")) { State.Remove("SearchTerm"); } if (State.ContainsKey("FilterIndex")) { State.Remove("FilterIndex"); } } else { State["SearchTerm"] = txtSearchTerm.Text; State["FilterIndex"] = listFilterType.SelectedIndex; } base.OnNavigatedFrom(e); } protected override void OnNavigatedTo(NavigationEventArgs e) { Debug.WriteLine("***\t In OnNavigatedTo function of SearchContactsPage\t ***"); base.OnNavigatedTo(e); if (State.ContainsKey("SearchTerm")) { txtSearchTerm.Text = State["SearchTerm"].ToString(); } if (State.ContainsKey("FilterIndex")) { listFilterType.SelectedIndex = (int)State["FilterIndex"]; } }

    In this page the user can search for specific contacts using either their display name, their email or their phone number. This is done using the Contacts chooser as we have seen in the previous screen. We will add functionality to initiate searches as the user types in the search term.

  37. Add the following bold-yellow-highlighted code to the class’s constructor after the InitializeComponent method call:

    C#

    public SearchContactsPage() { InitializeComponent(); Debug.WriteLine("***\t In constructor of SearchContactsPage\t ***"); SearchResults = new ObservableCollection<Contact>(); InitializeFilterDictionary(); contacts = new Contacts(); contacts.SearchCompleted += contacts_SearchCompleted; listFilterType.SelectionChanged += listFilterType_SelectionChanged; txtSearchTerm.KeyUp += txtSearchTerm_KeyUp; }

  38. Add the InitializeFilterDictionary method that will link the entries available to the user through the UI to actual contacts search types:

    C#

    private void InitializeFilterDictionary() { filterDictionary = new Dictionary<string, FilterKind>(); filterDictionary["Display Name"] = FilterKind.DisplayName; filterDictionary["Email"] = FilterKind.EmailAddress; filterDictionary["Phone Number"] = FilterKind.PhoneNumber; }

  39. Replace the following method to handle event handler function to handle the user selecting a different search method:

    C#

    void listFilterType_SelectionChanged(object sender, SelectionChangedEventArgs e) { InputScope scope = new InputScope(); InputScopeName name = new InputScopeName(); switch (filterDictionary[(listFilterType.SelectedItem as ListPickerItem).Content.ToString()]) { case FilterKind.DisplayName: name.NameValue = InputScopeNameValue.Default; scope.Names.Add(name); txtSearchTerm.InputScope = scope; break; case FilterKind.EmailAddress: name.NameValue = InputScopeNameValue.EmailNameOrAddress; scope.Names.Add(name); txtSearchTerm.InputScope = scope; break; case FilterKind.PhoneNumber: name.NameValue = InputScopeNameValue.Number; scope.Names.Add(name); txtSearchTerm.InputScope = scope; break; default: break; } QueueSearch(); }

    Note that the above method changes the keyboard displayed while entering the search term according to the search mode.

  40. Add the QueueSearch method. This method performs a search if one is not already in progress:

    C#

    private void QueueSearch() { lock (syncObject) { if (searchInProgress) { searchQueued = true; return; } searchInProgress = true; PerformSearch(); } }

  41. Add the PerformSearch method that performs the actual search:

    C#

    private void PerformSearch() { if (String.IsNullOrEmpty(txtSearchTerm.Text)) { // There is nothing to look for SearchResults.Clear(); searchInProgress = false; return; } contacts.SearchAsync(txtSearchTerm.Text, filterDictionary[(listFilterType.SelectedItem as ListPickerItem).Content.ToString()], null); }

  42. Add the txtSearchTerm_KeyUp event handler function to initiate a search as the user is typing:

    C#

    void txtSearchTerm_KeyUp(object sender, KeyEventArgs e) { QueueSearch(); }

  43. Localize and replace the contacts_SearchCompleted event handler function, which updates the UI once a search operation is completed:

    C#

    private void contacts_SearchCompleted(object sender, ContactsSearchEventArgs e) { Dispatcher.BeginInvoke(() => { try { SearchResults = new ObservableCollection<Contact>(e.Results); } catch { // Ignore exceptions, which are caused by searching for a // phone number with a string that does not contains digits // alone } finally { lock (syncObject) { searchInProgress = false; if (searchQueued == true) { // A search was queued while already searching. // Try performing it again. searchQueued = false; QueueSearch(); } } } }); }

  44. Press F5 to compile and run the application.
  45. Navigate to the “Search Contacts” page previously created and press the search button on the application bar:

    Figure 15

    Searching for contacts

    Select a search type and enter a search term to look for contacts.

  46. Press SHIFT+F5 to stop the debugging and return to the Visual Studio.

    This concludes the exercise and the lab.

    Note:
    The solution for this exercise is located at the Source \End folder of this lab.