Even though we can get caught up in the
mystique of our phones as being little computers, most users actually
use their phones to contact people and to keep their appointments.
So, as a developer, you might want to access the
user’s contacts and appointments to more tightly integrate your
applications into the phone experience. To do this, the Windows Phone
SDK supports entry points into the user’s contacts and appointments.
These APIs provide a way to search by commonly used search semantics but
also return results that can be further filtered using LINQ.
Note
All access to the contacts and appointments is
read-only. You cannot change, add, or delete any information in the
list of contacts or appointments.
Contacts
To access the contacts on a phone, you will start with the Contacts
class (in the Microsoft.Phone.UserData
namespace). This class provides the capability to perform an asynchronous search for the contacts on the phone:
var contactCtx = new Contacts();
contactCtx.SearchCompleted += (s, a) =>
{
IEnumerable<Contact> contacts = a.Results;
contactList.ItemsSource = results;
};
contactCtx.SearchAsync(null, FilterKind.None, null);
The SearchAsync
call takes three
parameters: a filter string, a type of filter, and an object that is
passed to the completed event (a state object). The first two parameters
typically allow you to do basic filtering in the underlying data store
and are specified as the most common filtering semantics. The FilterKind
enumeration tells the search API how to treat the filter string. For
example, to find a specific person you can search by name, like so:
contactCtx.SearchAsync("Chris Sells", FilterKind.DisplayName, null);
The FilterKind
enumeration includes the members shown in Table 1.
TABLE 1 FilterKind Enumeration
To use the FilterKind
enumeration, you can specify the filter and the FilterKind
enumeration in the SearchAsync
method:
contactCtx.SearchAsync("[email protected]",
FilterKind.EmailAddress,
null);
The result of a search is an IEnumerable
collection of Contact
objects. The Contacts
class supports all the information about a particular contact. Because
the data structure can be verbose (for example, there can be multiple
phone numbers, addresses, company names, and so on), the class supports
many collections for the various items in the data store. For instance,
you can get the simple display name as a property, but to get the phone
number of a contact you have to use LINQ against the collection:
IEnumerable<Contact> contacts = a.Results;
var contact = contacts.First();
string displayName = contact.DisplayName;
string phoneNumber = contact.PhoneNumbers.First().PhoneNumber;
This means that to meaningfully bind to the Contact,
you will likely need to create a class that represents the data you want to show:
public class ContactInfo
{
public string Name { get; set; }
public ContactPhoneNumber PhoneNumber { get; set; }
public DateTime? Birthday { get; set; }
public ContactCompanyInformation Company { get; set; }
public ImageSource Picture { get; set; }
public ContactEmailAddress EmailAddress { get; set; }
}
Although you can have the bindable class only include basic types, you can also use the built-in data structures (for example, ContactPhoneNumber
) because they do support binding. Luckily, building a collection of these bindable objects is easy using LINQ projections:
IEnumerable<Contact> contacts = a.Results;
var qry = from c in contacts
select new ContactInfo()
{
Name = c.DisplayName,
EmailAddress = c.EmailAddresses.FirstOrDefault(),
PhoneNumber = c.PhoneNumbers.FirstOrDefault(),
Company = c.Companies.FirstOrDefault(),
};
var results = qry.ToList();
By using this technique of projecting into a new list of the contact information, you can easily build a ListBox
using data binding:
<ListBox x:Name="contactList">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Width="470">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding EmailAddress.EmailAddress}" />
<TextBlock Text="{Binding Company.CompanyName}" />
<TextBlock Text="{Binding PhoneNumber.PhoneNumber}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You can see in the data
binding that you can navigate down the data structure to the object you
want to bind to. And because they are data bindings, if the user does
not have one of these objects, the binding silently fails (which is one
of the advantages of doing this in a binding).
Of course, because you can use LINQ to query against the resultant collection, you can use it if the FilterKind
enumeration does not give you the granularity you need to find
contacts. Although this works well, be aware that loading all contacts
in memory can consume a lot of memory. For example, if you want to find
all the contacts who have a phone number in the 206 area code (as well
as sort them by display name), you can just use LINQ:
var qry = from c in contacts
where c.PhoneNumbers
.Any(phone => phone.PhoneNumber.Contains("(206)"))
orderby c.DisplayName
select new ContactInfo()
{
Name = c.DisplayName,
EmailAddress = c.EmailAddresses.FirstOrDefault(),
PhoneNumber = c.PhoneNumbers.FirstOrDefault(),
Company = c.Companies.FirstOrDefault(),
};
In addition to the basic contact information, you can also ascertain which accounts the user contacts. The Contacts
class exposes a list of the user’s accounts. Each account will tell you
the kind of account (for example, Facebook, Microsoft Account, Outlook,
and so on) as well as the name of the account:
Account first = contactCtx.Accounts.FirstOrDefault();
if (first != null)
{
string accountName = first.Name;
if (first.Kind == StorageKind.Facebook)
{
MessageBox.Show("Your first account is Facebook");
}
}
You cannot filter by account through the SearchAsync
method, but you can filter any of the associated objects by account.
For instance, to filter contacts by account, simply use the following:
Account first = contactCtx.Accounts.FirstOrDefault();
var qry = from c in contacts
where c.Accounts.Contains(first)
select new ContactInfo()
{
Name = c.DisplayName,
EmailAddress = c.EmailAddresses.FirstOrDefault(),
PhoneNumber = c.PhoneNumbers.FirstOrDefault(),
Company = c.Companies.FirstOrDefault(),
};
Most of the objects related to contacts also
have an account associated with them. For example, if you have a
contact, you can find out which email address is from Facebook, like so:
Contact theContact = contacts.First();
var addr = theContact.EmailAddresses
.Where(email => email.Accounts
.Any(a => a.Kind == StorageKind.Facebook))
.FirstOrDefault();
Lastly, each contact can have a picture
associated with it. This picture is not immediately returned with the
contact (because that could occupy a large chunk of memory). To access
the picture, the Contacts
class has a GetPicture
method that returns a Stream
object containing the image. Although this is useful, you’ll probably need to wrap it with an ImageSource
object to use it in data binding. One approach is to do this during the projection into the bindable class, like so:
var qry = from c in contacts
select new ContactInfo()
{
Name = c.DisplayName,
EmailAddress = c.EmailAddresses.FirstOrDefault(),
PhoneNumber = c.PhoneNumbers.FirstOrDefault(),
Company = c.Companies.FirstOrDefault(),
Picture = CreateImageSource(c.GetPicture())
};
The implementation of the CreateImageSource
method that does the wrapping looks like this:
ImageSource CreateImageSource(Stream stream)
{
if (stream == null) return null;
var src = new BitmapImage();
src.SetSource(stream);
return src;
}
Because data binding can’t turn a Stream
into an image automatically, either you need to do that when you create
the object or perhaps you can do it with a value converter .
Appointments
Working with appointments follows the same pattern. It all starts with the Appointments
class (like the Contacts
class started with Contacts
):
var appointmentCtx = new Appointments();
appointmentCtx.SearchCompleted += (s,a) =>
{
IEnumerable<Appointment> appts = a.Results;
// Bind to the UI
apptList.ItemsSource = appts;
};
appointmentCtx.SearchAsync(DateTime.MinValue,
DateTime.Today,
null);
After creating the Appointments
class, you can handle the SearchCompleted
event and call the SearchAsync
method to perform a search. The SearchAsync
method enables you to specify a date range for the search (the last
parameter is an optional piece of state to send to the completed event).
The data returned by the search is an enumerable list of Appointment
objects.
Unlike the Contacts
class, the Appointments
class supports binding because the nature of the data is fairly simple. For example, by using data binding, a ListBox
to show appointments could look as simple as this:
<ListBox x:Name="apptList">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Width="470">
<TextBlock Text="{Binding StartTime, StringFormat=d}" />
<TextBlock Text="{Binding IsPrivate}" />
<TextBlock Text="{Binding Location}" />
<TextBlock Text="{Binding Subject}" />
<TextBlock Text="{Binding Status}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The SearchAsync
method also enables
you to specify an account to search as well as the maximum number of
results. So, you can select an account and then search like so:
var outlookAccount = appointmentCtx.Accounts
.Where(a => a.Kind == StorageKind.Outlook)
.FirstOrDefault();
appointmentCtx.SearchAsync(DateTime.MinValue, // Start
DateTime.Today, // End
25, // Max Results
outlookAccount, // Account
null);
By using these APIs, you can access the contacts and appointments for the phone’s user.