Mas Windows 8.1
Mas Windows 8.1
Mas Windows 8.1
By
Matteo Pagani
This book is available for free download from www.syncfusion.com on completion of a registration form.
If you obtained this book from any other source, please register and download a free copy from
www.syncfusion.com.
This book is licensed for reading only if obtained from www.syncfusion.com.
This book is licensed strictly for personal or educational use.
Redistribution in any form is prohibited.
The authors and copyright holders provide absolutely no warranty for any information provided.
The authors and copyright holders shall not be liable for any claim, damages, or any other liability arising
from, out of, or in connection with the information in this book.
Please do not use this book if the listed terms are unacceptable.
Use shall constitute acceptance of the terms listed.
SYNCFUSION, SUCCINCTLY, DELIVER INNOVATION WITH EASE, ESSENTIAL, and .NET ESSENTIALS are the
registered trademarks of Syncfusion, Inc.
Table of Contents
The Story behind the Succinctly Series of Books .................................................................................. 9
About the Author ....................................................................................................................................... 11
Introduction ............................................................................................................................................... 12
Chapter 1 Publishing an Application on the Store ............................................................................... 13
Localization ............................................................................................................................................ 13
Localizing images ................................................................................................................................ 15
Manage the default language .............................................................................................................. 15
Translating the applications name ...................................................................................................... 15
The trial mode ........................................................................................................................................ 16
Testing the trial mode .......................................................................................................................... 17
Purchasing the application ................................................................................................................... 19
In-app purchase ..................................................................................................................................... 20
Retrieving the list of available products ............................................................................................... 21
Purchasing a product ........................................................................................................................... 22
Managing a consumable product ......................................................................................................... 23
Checking the products status .............................................................................................................. 23
Testing in-app purchase ...................................................................................................................... 24
Publishing the application on the Store ................................................................................................. 26
Sharing the identity between two applications ..................................................................................... 26
How to create a developer account ..................................................................................................... 28
The Dashboard .................................................................................................................................... 29
The certification process ...................................................................................................................... 29
How to generate the package to publish ............................................................................................. 29
Publishing a Windows Phone app ....................................................................................................... 31
Whenever platforms or tools are shipping out of Microsoft, which seems to be about every other
week these days, we have to educate ourselves, quickly.
Free forever
Syncfusion will be working to produce books on several topics. The books will always be free.
Any updates we publish will also be free.
10
11
Introduction
The Succinctly series aims to provide valuable information in short, standalone volumes.
Occasionally, a topic will prove so expansive that a single volume is not enough to adequately
address the most important points. In Windows 8.1 Succinctly, you were guided through the
process of developing apps for both Windows 8.1 and Windows Phone 8.1. Now that you know
how to build functioning apps, its time to get them into the hands of users.
In More Windows 8.1 Succinctly, you will learn how to publish apps, work with the network, and
much more. By the end of the book, you will be ready to send your app into the world for users
to enjoy. If you have not yet read Windows 8.1 Succinctly, we strongly recommend you do so to
ensure you are ready for these advanced topics. If you have read the previous e-book and are
ready to push your skills even further, proceed to Chapter 1.
12
Localization
An effective strategy to make our application more popular is to properly support multiple
languages. Windows and Windows Phone devices are used all over the world and by all kinds
of users, so we cant take for granted that our users are able to read and speak English.
Windows Store apps offer built-in support for localization. The difference from the traditional
approach is that we wont hard-code our texts in the XAML or in codewell save them in
separate files, one for every supported language. Every file (which, under the hood, are XML
files) contains a list of resources, with a key (the unique identifier) and the value (the real text
localized in the specific language). Every time wed like to display a text to the user, were going
to add a reference to the key that matches the text that we want to display.
The first step to manage localization is to add a file for each language we want to support. This
scenario is implemented using a naming convention based on the folders name. All the
resource files need to be included in a folder in the project (it could also be in the Shared
project, if youre developing a Universal app), usually called strings. Inside this folder, youll
need to create a subfolder with the culture code for each language you want to support.
You can find a list of supported codes in the MSDN documentation:
http://msdn.microsoft.com/en-us/library/windows/apps/hh202918(v=vs.105).aspx. Its important
to highlight that youre not forced to use the full culture code (like en-US or it-IT); you can also
use the short version (like en or it) if you want to support all the variants of the same culture
with one unique resource file.
Inside each folder, youll need to create a resource file; youll find a specific template in Visual
Studio, called Resources File (.resw). The default name assigned is Resources.resw.
13
If you double-click on the Resources.resw file, Visual Studio will open the Visual Editor, which
will display a list of rows and three columns:
Resources are strictly connected to the XAML controls that will display the value; consequently,
the Name follows a specific convention. The first part of the key is the unique resource identifier;
its up to you to choose whats best for your scenario. For example, it could be a label like
Title. The second part of the key, separated by the first part with a period, is the name of the
controls property we want to handle with this resource. For example, if we want to display the
value of the resource in the Text property of a TextBlock control, we should define it as
Title.Text.
How can we connect the resource to its control? By using a special XAML property called
x:Uid, which is supported by every XAML control. We need to set it with the unique identifier of
the resource, which is the first part of the key (before the period). For example, if we want to
connect the resource Title.Text to display its value in a TextBlock control, we need to define
it in the following way:
<TextBlock x:Uid="Title" />
Another common requirement is to use a resource in code. For example, if we need to display a
pop-up message using the MessageDialog class, we need to access the resource in another
way, since a pop-up message cant be defined in XAML. To achieve this goal, we need to use
the ResourceLoader class, which offers a method called GetString(). As the parameter, we
need to pass the full name that identifies the resource. The application will automatically retrieve
the resource based on the current language.
private async void OnShowMessageClicked(object sender, RoutedEventArgs e)
{
ResourceLoader loader = new ResourceLoader();
string resource = loader.GetString("Title/Text");
MessageDialog dialog = new MessageDialog(resource);
await dialog.ShowAsync();
}
As you can see, theres an important difference to highlight: when you call a resource from
code, you need to use the forward slash (/) instead of the period as a separator between the
first and the second part of the key. In the sample, to retrieve the value of the resource with the
name Title.Text, we pass as the parameter the value Title/Text.
14
Localizing images
Another common requirement is to support different images according to the users language. In
this case, we can use a naming convention, similar to the one we saw in Chapter 4, to manage
the different scale factors. We can add a .lang- suffix to the image name, followed by the culture
code, to make it visible only when the device is used with the specified language. For example,
if we have an image called logo.png and we want to have two versions, one for English and
one for Italian, we would have to add two images to the project: one with name logo.langen.png, and another with name logo.lang-it.png.
This naming convention is completely transparent to the developer. In XAML or in code, well
just need to reference the image using the base name (logo.png), and the operating system will
automatically pick the proper one, according to the language. Heres an example:
<Image Source="/Assets/logo.png" />
15
Manually: We have an API that returns a Boolean that simply tells us if the application
is running in trial mode. With this information, we can implement the trial in the way we
prefer; we could block some features, display a pop-up message every time the app is
launched, display some advertising, etc.
Time trial: When you submit your application in the Store, youll be able to set an
expiration date for the trial, and the app will be automatically blocked once the trial
period is ended. This mode is available only on Windows, since the Windows Phone Dev
Center doesnt allow you to set an expiration date during the submission.
Both ways are managed by a class called LicenseInformation, which is a property of the
CurrentApp class that belongs to the Windows.ApplicationModel.Store namespace. There
are two properties that we can use to manage the trial:
IsTrial is a Boolean value that returns if the application is running in trial mode or not.
IsActive is a Boolean value that returns if the application is expired or not. If we want
to discover the expiration date, we can access to the ExpirationDate property.
The following sample code shows a pop-up message with the current status of the application:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
LicenseInformation license = CurrentApp.LicenseInformation;
if (!license.IsActive)
{
MessageDialog dialog = new MessageDialog("The application is
expired");
await dialog.ShowAsync();
}
else if (license.IsTrial)
{
MessageDialog dialog = new MessageDialog("The application is in trial
mode");
await dialog.ShowAsync();
}
}
16
17
<CurrentMarket>en-US</CurrentMarket>
<AgeRating>3</AgeRating>
<MarketData xml:lang="en-us">
<Name>AppName</Name>
<Description>AppDescription</Description>
<Price>1.00</Price>
<CurrencySymbol>$</CurrencySymbol>
<CurrencyCode>USD</CurrencyCode>
</MarketData>
</App>
</ListingInformation>
<LicenseInformation>
<App>
<IsActive>true</IsActive>
<IsTrial>true</IsTrial>
<ExpirationDate>2015-01-19T05:00:00.00Z</ExpirationDate>
</App>
</LicenseInformation>
</CurrentApp>
The section we need to use to test the trial mode is inside the LicenseInformation block,
which contains an App section with a set of elements that matches the properties weve
previously seen as exposed by the CurrentApp class (which are IsActive, IsTrial and
ExpirationDate). We can edit this file and save it; from now on, the CurrentAppSimulator
class will use these properties to return the applications status. The previous sample defined an
application with a trial mode that expires on January 19th 2015 at 5 AM.
Note: If you try to use the previous sample code that sets the ExpirationDate on Windows
Phone, you will notice that the code will work fine. The APIs are indeed supported by the
platform, but since the submission process doesnt support this scenario, you wont be able
to use it in a published application.
18
19
<Description>Description</Description>
<Price>2.00</Price>
<CurrencySymbol></CurrencySymbol>
<CurrencyCode>EUR</CurrencyCode>
</MarketData>
</App>
The previous sample simulates an application priced at 2 euro. When we use the
RequestAppPurchaseAsync() method of the CurrentAppSimulator class, Windows and
Windows Phone will display a special screen to simulate the purchase process. We will be able
to choose one of the supported statuses, which are S_OK (purchase completed with success),
E_INVALID, E_FAIL or E_OUTOFMEMORY (different error conditions).
In-app purchase
Another way to monetize our application is to support in-app purchases: instead of making a
paid application, we can make a free one (or a paid one, but at a cheaper price) that gives the
user the option to purchase new features or items within the application itself. For example, a
photo-editing application can give access to a basic set of filters, and then allow the user to buy
more of them using in-app purchase.
Microsoft supports the in-app purchase feature directly with the Store, so you wont need to
manage payments yourself. The user will make in-app purchases by using the same credit card
connected to their account, and the experience will be very similar to the one offered when they
buy an app. For the developer, the revenue-sharing mechanism is the same: Microsoft will keep
30 percent of the price, and the developer gets the rest. However, you are not forced to use
Microsoft services to manage in-app purchases: if you already have your own payment system,
you can freely use it without sharing the income with Microsoft.
When you use the Microsoft services, you can define two types of purchases:
Durable: Once the product is purchased, it becomes users property, and it will be
maintained even if he uninstalls the application or changes his device. Its typically used
when the purchase is used to unlock an application feature (a new set of levels for a
game, the advertising removal, etc.).
Consumable: These products can be consumed and purchased multiple times. They
are typically used in games to manage virtual money or temporary features (like a power
up).
The available products are defined in the Windows Dev Center during the submission process,
in the Services step. In the Windows Phone Dev Center, instead, theres a specific section
called Products, where you can add new products by clicking on the Add in-app product
button.
20
One important feature to highlight about in-app purchases is that they are strictly connected to
the Universal App concept. If youre going to publish a Universal project and enable identity
sharing (so youll have the same application on both the Windows and the Windows Phone
stores), the in-app purchases will be shared among the two platforms. This way, if the user has
purchased a product on her smartphone, she will also find it available on her tablet or computer.
However, since the stores are still separate, you will have to define the products on both Dev
Centers.
Every product is identified by a set of parameters:
A name
A unique identifier, which is used in the application to reference it
The type (durable or consumable)
The expiration date: If we set it, the product will be no longer purchasable after that date.
The language
The price
The countries where its available.
A title and description for every supported language.
Once youve defined the products, were going to use the same CurrentApp class weve
previously seen to manage the trial experience.
21
Purchasing a product
The product purchase is made by using another method of the CurrentApp class,
RequestProductPurchaseAsync(). As the parameter, we need to pass the unique product
identifier weve defined in the portal. The following sample shows how to manage the
ItemClick event of the previous ListView control that is triggered when the user has tapped
on one of the products:
private async void Products_OnItemClicked(object sender,
SelectionChangedEventArgs e)
{
Guid productTransactionId;
ProductListing selectedProduct = e.ClickedItem as ProductListing;
PurchaseResults result = await
CurrentApp.RequestProductPurchaseAsync(selectedProduct.ProductId);
switch (result.Status)
{
case ProductPurchaseStatus.Succeeded:
{
productTransactionId = result.TransactionId;
MessageDialog dialog = new MessageDialog("The product has been
succesfully purchased");
await dialog.ShowAsync();
break;
}
case ProductPurchaseStatus.AlreadyPurchased:
{
MessageDialog dialog = new MessageDialog("The product has already
been purchased");
await dialog.ShowAsync();
break;
}
case ProductPurchaseStatus.NotFulfilled:
{
MessageDialog dialog = new MessageDialog("The product hasnt been
fulfilled yet and its still valid");
await dialog.ShowAsync();
break;
22
}
}
}
The method returns a PurchaseResults object, which offers a property called Status. Its a
ProductPurchaseStatus enumerator, with a value for each different scenario. In the previous
sample, we manage three situations:
Its up to us to manage these responses in the proper way; in the previous sample, we just
display a message to the user. In a real application, when we receive the Succeeded response,
we would download or unlock the purchased feature.
23
24
ProductType="Consumable">
<MarketData xml:lang="en-us">
<Name>Power up</Name>
<Price>1.00</Price>
<CurrencySymbol></CurrencySymbol>
<CurrencyCode>EUR</CurrencyCode>
</MarketData>
</Product>
</ListingInformation>
<LicenseInformation>
<App>
<IsActive>true</IsActive>
<IsTrial>false</IsTrial>
</App>
</LicenseInformation>
</CurrentApp>
After the App section, we find a series of Product items; each of them identifies a product that
can be purchased in the application. The most important information is set using attributes, like
ProductId (the product identifier), LicenseDuration (we can set it to 0 if it doesnt expire) and
ProductType, which can be Durable or Consumable.
Inside every Product section, we have a tag called MarketData, which contains all the
information about the product purchase, and will be displayed to the user when they try to buy
the app. When you trigger a purchase using the simulator, the experience will be similar to the
trial one: a pop-up window will allow you to simulate one of the supported statuses of the
purchase process.
25
You can also simulate if a product has been already purchased or not, by adding a new
Product item in the LicenseInformation section of the XML file, like in the following sample:
<LicenseInformation>
<App>
<IsActive>true</IsActive>
<IsTrial>true</IsTrial>
<ExpirationDate>2016-01-19T05:00:00.00Z</ExpirationDate>
</App>
<Product ProductId="RemoveAdvertising">
<IsActive>true</IsActive>
</Product>
</LicenseInformation>
By using this configuration, the product identified by the RemoveAdvertising id will be already
active. If we want to test, instead, a consumable product, we need to add a new section called
ConsumableInformation under the LicenseInformation section, like in the following sample:
<ConsumableInformation>
<Product ProductId="PowerUp" TransactionId="00000000-0000-0000-0000000000000000" Status="Active" />
</ConsumableInformation>
Purchase sharing: If the application has been purchased on one of the two platforms,
the user wont have to pay again to download it on the other one.
26
In-app purchases sharing: In-app purchases are shared, so that a product bought on
one platform is available for free on the other one.
Roaming storage synchronization: In Chapter 5, we learned how to use the roaming
storage, where content is automatically synchronized across different devices. If the
applications are sharing the same identity, this feature is extended to different platforms.
Using a single push notification channel: When two applications are sharing the
same identity, we can use the same channel to send a push notification to both
platforms, by using the mechanism we learned in Chapter 10.
All these features are managed with the Microsoft account, which should be configured the
same as the primary account for all the users devices, whether they are smartphones, tablets,
or computers. To enable identity sharing, youll have to reserve the same name for both
applications. When you start to submit a new Windows or Windows Phone application, the first
step will ask you to choose a name for the application, which should be unique on the Store
(this requirement was only recently introduced for Windows Phone, so you may find apps with
the same name).
In the first submission step, you can reserve a new name, or choose, from a dropdown menu,
one that youve already reserved for another application. When you choose the name of an
application that has been already published on another platform, the portal will show you a
warning that the applications will be linked and the identity shared. Its important to make sure
that were connecting the right apps before pressing the Associate app button, since its not
possible to unlink two linked apps.
Figure 3: The warning message that is displayed when you try to link two applications
When two apps are published as universal and sharing an identity, they will be displayed on the
Store with a special icon near the price, as you can see in the following image:
27
The Payout section is required to set up how we want to be paid by Microsoft. We can
set up a bank account or a PayPal account. Microsoft will pay us every time we reach
the income amount of $200.
The second section is required to properly manage the taxes payout, to avoid being
taxed by both the U.S. and your country. If youre unsure about this section, I suggest
you contact a business consultant for help.
28
The Dashboard
Both portals offer a main section called Dashboard, which gives you quick access to all the Dev
Center sections. You can start a new submission, see the details about the already published
apps, and get statistics about download numbers and the number of sold apps. The main
access to the Dev Center is found at http://dev.windows.com. After youve logged in clicked on
the Dashboard link, you will be asked which Dev Center you want to access.
Technical: The application needs to be stable and fast; it needs to run correctly on all
the devices, regardless of the available memory and the CPU speed; and it needs to
properly support different resolutions and form factors.
Contents: There are some contents that are not allowed in a Windows Store app, like
pornographic images, excessive violence, racism, religious discrimination, etc.
Guidelines: In this book, weve learned that when we develop an application, there are
some guidelines that we need to follow.
Certification times are not fixed: in most cases, only automatic tests are performed, which
means that the application will be certified in a few hours. However, it can happen also that the
application is picked up for a deeper testing; in this case, it can take up to five business days. If
the certification process is successfully completed, the application will be published to the Store
(unless youve opted for a manual publishing, as well see later). In case of failure, you will
receive a detailed report with all the problems that have been found, and the exact steps to
reproduce them.
29
Figure 4: The option to link a package to a specific application on the Dev Center
The next step will ask you for information about the package, such as:
Now the procedure is complete. After you click the Create button, the wizard will build the
package, by providing an .appxupload or .appxbundle file to upload during the submission
process. After the generation is complete, we have the chance to run the Windows App
Certification Kit, which will launch a series of basic tests that will help us make sure that the
certification process wont fail (at least from a technical point of view).
30
Public Store: Its the standard approach; the application is publicly available on the
Store and can be found and installed by any user.
Hide from users browsing or searching the Store: The application is still published on
the Store, but the user wont be able to search for it or it wont appear in the Store
rankings. Users will be able to install the application only by using the direct Store link.
Beta: This channel is used when we want to get feedback for our application, and we
want to distribute it to a selected numbers of testers. As with the Hide option, the
application wont be visible in the Store; youll be able to find it only with the direct link. In
addition, not all the users will be able to download it. We will have to provide a list of
allowed people by specifying the Microsoft Account registered with their devices. The
step offers a specific area for this purpose, where we can enter all the accounts
separated by a semi-colon. If an unauthorized user will tries to install the application, the
Store will display an error message. An important feature of the beta submission is that it
doesnt require certification; it only needs to pass the technical tests. Since were
publishing the app for testing purposes, it makes sense that its not fully completed yet,
and thus, it can have some bugs.
Its important to highlight that the previous distribution channels are available only on Windows
Phone apps. At the time of writing, the beta distribution is not available for Windows apps.
31
The last option we can set, using the Publish section, is when we want to publish the app. We
can publish immediately after the certification process has been passed, or we can manually
publish it (for example, because we want to start a marketing campaign first).
32
The second part of the section will ask for all the metadata that describe the application, which
needs to be specified for every supported language. Youll find a dropdown menu that you can
use to choose the language youre going to define.
The requested information is:
Description: The text displayed to the user that describes our application. The text can
have a maximum length of 2,000 chars.
Update description: If were submitting an update, we can describe whats changed in
this new version. Also in this case, the maximum length is 2,000 chars.
Keywords: We can include up to five keywords, which are used on the Store to make it
easier for to users to search for your application.
The last section collects all the images that are displayed on the Store:
Optional steps
The submission procedure also offers three optional steps:
Add in-app advertising: This step is used if you want to display advertising in your app
using PubCenter, which is Microsofts advertising provider. This step will give you the
credentials required to properly register the advertising control in your pages.
Market selection and custom pricing: Weve seen how, in Step 1, we were able to
choose the price and the countries where we will sell our application. The same price is
applied in every country and automatically converted based on the local currency. With
this step, we can choose to distribute the application only in some countries, or to sell it
in some of them at a different price than the base one weve chosen.
Map services: Weve already talked about this step in Chapter 7. Its required if were
using the map control in our application, since it provides the required credentials to
register it.
33
34
35
The price.
If we support trial mode: If the answer is yes, we can decide to set an expiration date.
If the application allows in-app purchases: If the answer yes, we can choose if we want
to use the Microsoft services (in this case, well be redirected to the next step to define
the products), or if we want to rely on our payment services.
The markets where we will sell the application.
The release date: The default option is Release my app soon as it passes
certification, but we can also schedule a specific date and time.
The category and the optional subcategory.
The hardware requirements.
If we support accessibility, which means that the application has been specifically
designed to be easy to use for people with disabilities.
Step 3: Services
Weve already seen this step in Chapter 10, when we introduced push notifications. In this
section, we can set up the push notification services (by using Mobile Services provided by
Azure, the cloud Microsoft solution, or by retrieving the authentication tokens to be used in our
backend) and define the in-app purchase products.
Step 5: Cryptography
To be compliant with the US laws about cryptography, we need to declare if our application
uses any algorithm to encrypt data like a digital signature, NTLM or SSL protocols, DRM, etc.
Step 6: Packages
This section is very important, since it allows developers to upload the .appxupload or
.appxbundle packages that have been created by Visual Studio. You can simply drag and drop
the files in the page or manually look for them on your computer by clicking the Browse to files
button. Once youve chosen the file to upload, youll need to wait for the operation to be
completed before moving on. This section can be used to upload packages both for Windows 8
and Windows 8.1. Since this book is about Universal Windows apps (which are based on
Windows 8.1), we wont describe how to manage Windows 8 packages.
Step 7: Description
This is another important step, since it requires the developer to define all the information that
will be displayed on the Store to the user. This section is split into two parts: the first one is
unique, while the second one is different according to whether were uploading a Windows 8 or
a Windows 8.1 application. As weve already stated, since this book is about Universal Windows
app, we will talk only about the procedure to describe a Windows 8.1 application.
36
Description: The text that describes the application. The maximum length is 10,000
chars.
App features: We can specify up to 20 features of our application that we want to
highlight.
Screenshots: We can upload up to nine screenshots that display how the application
looks. The minimum required resolution is 1366x768. For every screenshot, we also
need to include a description, with a maximum length of 200 chars.
Notes: Its a text (maximum length 1,500 chars) which describes the new features
added in this version. Its especially useful when youre submitting an update for an
existing application.
Recommended hardware: You can include a list of 11 features that describe the
minimum hardware requirements for the application.
Keywords: You can add up to seven keywords that are used to improve the app
discoverability on the Store.
Copyright and trademark info: The information about who has the legal rights to the
applications content.
Promotional images: A set of optional images that are used if your application is
chosen to be promoted on the Store.
Website: The website connected to the application.
Support contact info: The developers mail address, to get support in case of issues.
Privacy policy: If we are publishing an application that uses an Internet connection, we
need to provide a privacy policy, both within the app and on a website. This field
contains the URL of the privacy policy page.
37
Youll only have to change the information that you want to modify For example, if youre
submitting an update, youll have to access to the Packages section and replace the existing
package with the new one. Alternatively, if you want to change the description, you can go
directly into Step 7. In any case, a new submission will be sent to the Store; if there are
important changes (like a new package), a new certification process will be triggered.
Deploying on Windows
The Visual Studio wizard will create a folder next to the .appxupload/.appxbundle package,
which contains a set of additional files. To manually deploy the application, well need to copy
this folder on another device and installing it by using the PowerShell script that is included in
the folder. The script will take of retrieving the proper certificate, activating the developer license
(the user will be required to login with his Microsoft Account, in the same way we do when we
create our first project in Visual Studio) and installing the application.
To proceed with the deployment youll have to right-click on the Add-AppDevPackage.ps1 file
included in the folder and choose the Run with PowerShell option: the script will be executed
and it will install the application for you.
38
In addition, the computer or the tablet has to be allowed to install applications without using the
Store: on a computer with a Visual Studio 2013 installation, this policy is automatically applied.
However, you can manually apply it by editing a key in the Windows registry. Open the Start
screen, write regedit and launch the editor: now look for a key called
HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\. It will contain a value
called AllowAllTrustedApps: set it to 1 and close the editor.
39
The deployment is performed with a tool, installed with Visual Studio 2013 Update 2, called
Windows Phone Application Deployment 8.1 that you can find in the list of installed
applications on your computer. Its very simple to use: from the Target dropdown menu you can
choose the target device where to deploy the app (it can be a real device connected to the
computer or one of the available emulators). Then you just need to click on the Browse button
and look for the .appxupload or .appxbundle package generated by Visual Studio. In the end,
you have to click the Deploy button to perform the operation.
40
41
42
Note: The Windows Runtime class offers two HttpClient classes; the old one is part of the
Microsoft.Net.Http namespace and it has been added in Windows 8. The new one is part
of the Windows.Web.Http namespace and it has been introduced in Windows 8.1. In this
chapter, we will focus on the new one.
Download Operations
Typically, download operations are performed by using a GET command. Consequently, you
can use the GetAsync() method offered by the HttpClient class. To simplify the developers
work, the HttpClient class exposes some methods to download the most common data types
such as GetStringAsync() (to download text data such as XML or JSON) or
GetBufferAsync() (to download binary data such as an image).
Downloading textual content is simple: just pass to the GetStringAsync() method the URL of
the text to download:
private async void OnStartDownloadClicked(object sender, RoutedEventArgs e)
{
HttpClient client = new HttpClient();
string result = await client.GetStringAsync(new
Uri("http://feeds.feedburner.com/qmatteoq", UriKind.Absolute));
Result.Text = result;
}
The method simply returns the downloaded string. In the sample, it is displayed on the page by
using a TextBlock control.
Otherwise, if you need to download a binary content, you can use the GetBufferAsync()
method, like in the following sample:
private async void OnStartDownloadClicked(object sender, RoutedEventArgs e)
{
HttpClient client = new HttpClient();
IBuffer buffer = await client.GetBufferAsync(new
Uri("http://www.website.com/image.png", UriKind.Absolute));
StorageFile storageFile = await
43
ApplicationData.Current.LocalFolder.CreateFileAsync("picture.png",
CreationCollisionOption.ReplaceExisting);
await FileIO.WriteBufferAsync(storageFile, buffer);
}
By using the WriteBufferAsync() method of the FileIO class we learned to use in Chapter 5,
we can save an image downloaded from the web to the local storage of the app.
The HttpClient class also offers a way to have more control over the download operation by
using a generic method called GetAsync(). In this case, you will not get the resources content
directly in return but a HttpResponseMessage object instead, which contains all of the
properties that define the HTTP response (such as headers, status, etc.). The following code
shows the same previous sample (downloading an image and saving it to the store) but
performed with generic method:
private async void OnStartDownloadClicked(object sender, RoutedEventArgs e)
{
try
{
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync(new
Uri("http://www.website.com/image.png", UriKind.Absolute));
response.EnsureSuccessStatusCode();
IBuffer buffer = await response.Content.ReadAsBufferAsync();
StorageFile storageFile = await
ApplicationData.Current.LocalFolder.CreateFileAsync("picture.png",
CreationCollisionOption.ReplaceExisting);
await FileIO.WriteBufferAsync(storageFile, buffer);
}
catch (Exception exc)
{
Error.Text = exc.Message;
}
}
Since the GetAsync() method is generic, we do not directly receive in return the content
of the resource but, rather, a generic HttpResponseMessage object with the response.
(To get the real content, we need to access the Content property, which offers some
methods to read it based on the type. In this sample, since we are downloading an
image (which is a binary content), we call the ReadAsBufferAsync() method.)
We can see one of the advantages of using the generic method: the
HttpResponseMessage object offers a method called EnsureSuccessStatusCode(),
which raises an exception in case something went wrong during the operation. (For
example, the resource was not available so the request returned a 404 error. This way,
44
we make sure that we can properly catch any error before performing any operation with
the content.)
Upload Operations
Upload operations are usually performed with a POST HTTP command. Consequently, we can
use the PostAsync() method offered by the HttpClient class. To define the content of the
request that we want to send, the Windows Runtime offers a generic interface called
IHttpContent. In the run time, you can find many objects that already implement this class
such as HttpStreamContent (to send a binary file), HttpStringContent (to send a text) or
HttpMultipartFormDataContent (to send a web form). The following sample shows how to
send a file to a web service:
private async void OnUploadFileClicked(object sender, RoutedEventArgs e)
{
StorageFile storageFile = await
ApplicationData.Current.LocalFolder.GetFileAsync("picture.png");
IRandomAccessStream accessStream = await
storageFile.OpenAsync(FileAccessMode.ReadWrite);
HttpClient client = new HttpClient();
IHttpContent content = new HttpStreamContent(accessStream.GetInputStreamAt(0));
await client.PostAsync(new Uri("http://wp8test.azurewebsites.net/api/values",
UriKind.Absolute), content);
}
By using the APIs we learned in Chapter 5, we retrieve the stream with the content of an image
stored in the local storage. Then, since we are dealing with a file (i.e., binary content) we create
a new HttpStreamContent object, passing as parameter the contents stream. In the end, we
simply call the PostAsync() method passing as parameters the URLs to where you want to
perform the upload and to the content you want to send.
45
As you can see, the biggest difference with the previous code is that we are invoking the
GetAsync() method without using the await prefix. This way, instead of immediately starting
the download, we simply get a reference to the Task. This way, we are able to subscribe to the
Progress event, which provides in the parameters some useful properties to understand the
status of the operation:
In the event handler, thanks to this information, we are able to calculate the percentage of the
operation status and, similar to the previous sample, we can assign it to the Value property of a
ProgressBar control to visually display the status to the user. Please note that we have used
the Dispatcher to perform this operation; the Progress event is, in fact, executed on a
separate thread while the ProgressBar control is managed by the UI thread.
46
Once we have defined the Progress event, we can effectively start the network operation by
invoking the operation object with the await prefix. From this point, the code will proceed as
usual; when the operation is completed, we get the content of the downloaded file and can store
it in the local storage.
47
After we create a BackgroundDownloader object, we can define the download by calling the
CreateDownload() method, which requires as parameters the source URL (the file to
download) and a StorageFile object, which represents the file where you want to write the
downloaded content. In the previous sample, we created a file called demo.zip in the local
storage in which we want to save our content. The download operation is queued by using the
StartAsync() method. From now on, the OS will be in charge of managing it for us.
Since you are going to use this class especially to download big files, it is more important than
ever to display the status to the user by using a progress bar. We can do this by using an
approach similar to the one we used with the HttpClient class, as you can see in the following
sample:
private async void OnStartDownloadClicked(object sender, RoutedEventArgs e)
{
StorageFile file = await
ApplicationData.Current.LocalFolder.CreateFileAsync("demo.zip",
CreationCollisionOption.ReplaceExisting);
Uri uri = new
Uri("http://www.communitydays.it/content/downloads/2014/w807_demo.zip",
UriKind.Absolute);
BackgroundDownloader downloader = new BackgroundDownloader();
DownloadOperation download = downloader.CreateDownload(uri, file);
Progress<DownloadOperation> progress = new Progress<DownloadOperation>();
progress.ProgressChanged += progress_ProgressChanged;
await download.StartAsync().AsTask(progress);
}
void progress_ProgressChanged(object sender, DownloadOperation e)
{
double bytesReceived = e.Progress.BytesReceived;
double totalBytes = e.Progress.TotalBytesToReceive;
double percentage = (bytesReceived / totalBytes) * 100;
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Progress.Value = percentage;
});
}
48
The only difference is that, in this case, we use the BackgroundUploader class that offers the
CreateUpload() method to define the upload operation, which requires the file to reach the
destination URL and the file to send (represented, as usual, by a StorageFile object).
However, we also have the chance to send multiple files with one single operation, as you can
see in the following sample:
private async void OnStartUploadClicked(object sender, RoutedEventArgs e)
{
List<BackgroundTransferContentPart> files = new
List<BackgroundTransferContentPart>();
FileOpenPicker picker = new FileOpenPicker();
var selectedFiles = await picker.PickMultipleFilesAsync();
if (selectedFiles != null)
{
foreach (StorageFile selectedFile in selectedFiles)
{
BackgroundTransferContentPart part = new BackgroundTransferContentPart();
part.SetFile(selectedFile);
files.Add(part);
}
}
BackgroundUploader uploader = new BackgroundUploader();
StorageFile file = await
ApplicationData.Current.LocalFolder.GetFileAsync("demo.zip");
Uri destination = new Uri("http://www.qmatteoq.com/upload.aspx",
UriKind.Absolute);
UploadOperation operation = await uploader.CreateUploadAsync(destination, files);
await operation.StartAsync();
}
49
We are using the FileOpenPicker object that we learned to manage in Chapter 5. Thanks to
the PickMultipleFileAsync() method, the user is able to select multiple files from his or her
phone that are returned to the app in a collection. For every file in the collection, we create a
new operation part, identified by the BackgroundTransferContentPart class. We add the file
simply by calling the SetFile() method and then we add the part to a collection we previously
defined.
In the end, we can use an override of the CreateUploadAsync() method of the
BackgroundUploader class which accepts (instead of a single file) a collection of
BackgroundTransferContentPart objects. The work is done. By calling the StartAsync()
method, we will start the upload of all of the specified files.
50
By using the GetCurrentDownloadsAsync() method, we retrieve the list of all of the download
operations that belong to the app. Then, for each of them, we create a new
Progress<DownloadOperation> object, we subscribe to the ProgressChanged event, and we
call the AttachAsync() method to perform the reconnection. You will notice that the
ProgressBar control will not start from the beginning but, instead, from the last known position
before the app was terminated.
Of course, you can also use the same approach for upload operations. In this case, we will need
to use the GetCurrentUploadsAsync() method of the BackgroundUploader class.
REST Services
Today, the most common approach to creating web services for mobile apps is by using
Representational State Transfer (REST) services. They are popular for two main reasons:
They rely on the HTTP protocol so the operations are defined by using the standard
commands of the protocol such as GET or POST
They return the data by using standard formats such as XML or JSON
Consequently, virtually any technology is able to interact with web services. No matter which
platform and language you have chosen to write your mobile app in, you will always have the
chance to perform HTTP requests and to parse XML or JSON data.
51
When it comes to Windows Store apps, the easiest way to interact with REST services is by
using the HttpClient class we previously saw since it already offers a method for every HTTP
command. For example, we are going to use the GetAsync() method to retrieve some data
from the service or the PostAsync() one to send a new item that needs to be stored on the
backend. Since REST services return the data by using XML or JSON, we can reuse the
knowledge we acquired in Chapter 5 about serialization. Thanks to the
DataContractSerializer and DataContractJsonSerializer classes, we are able to
automatically convert the plain data into a collection of objects, which we can manipulate in our
Windows Store app. And vice versa: when we need to send some data to the service, we need
to convert our objects into a XML or JSON structure.
For example, lets say that we need to interact with a REST service, which is connected to a
database that contains a set of customers. One of the operations offered by the service is to
return a list of people, like in the following sample:
[
{
"Id":1,
"Name":"Matteo",
"Surname":"Pagani"
},
{
"Id":2,
"Name":"John",
"Surname":"Doe"
}
]
As we did in Chapter 5, the first thing we will need now is a class that maps this JSON data:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
By combining what we have learned at the beginning of this chapter about the HttpClient
class and the usage of the DataContractJsonSerializer class in Chapter 5, it should be
easy to understand the next code sample:
private async void OnConsumeServiceClicked(object sender, RoutedEventArgs e)
{
HttpClient client = new HttpClient();
string result = await client.GetStringAsync(new
52
Uri("http://wp8test.azurewebsites.net/api/values", UriKind.Absolute));
using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(result)))
{
DataContractJsonSerializer serializer = new
DataContractJsonSerializer(typeof(
List<Person>));
List<Person> people = serializer.ReadObject(ms) as List<Person>;
}
}
By using the GetStringAsync() method of the HttpClient class, we retrieve the JSON
response from the service. Then, by using the DataContractJsonSerializer class and the
ReadObject() method, we convert the JSON data into a collection of Person objects. Since the
ReadObject() method requires a stream as input and not directly the JSON string, we first
need to convert it into a MemoryStream object.
And vice versa: here is how we can perform a POST operation to send some data to the
service:
private async void OnPostDataClicked(object sender, RoutedEventArgs e)
{
Person person = new Person
{
Name = "Matteo",
Surname = "Pagani"
};
DataContractJsonSerializer serializer = new
DataContractJsonSerializer(typeof(Person));
MemoryStream stream = new MemoryStream();
serializer.WriteObject(stream, person);
string result = Encoding.UTF8.GetString(stream.ToArray(), 0, (int)stream.Length);
IHttpContent content = new HttpStringContent(result);
HttpClient client = new HttpClient();
await client.PostAsync(new
Uri("http://wp8test.azurewebsites.net/api/value",UriKind.Absolute), content);
}
In this case, we are performing the opposite operation. We take our objects (in this case, it is a
sample Person object) and we convert it into a JSON structure using the WriteObject()
method of the DataContractJsonSerializer class. Again, we need to use a MemoryStream
since the method is able to write the result only to a stream and not directly to a string. In the
end, we execute the PostAsync() method of the HttpClient class. Since the service accepts
the data as a string, we encapsulate the JSON into a HttpStringContent object that is passed
as parameter of the method, together with services URL.
53
Tip: When it comes to defining the mapping between a class and JSON, the operation
sometimes is not easy to accomplish, especially if the JSON is complex. To make this
process easier, Visual Studio offers a feature can do this for you; just copy in the clipboard
the JSON data returned by your service and choose Edit -> Paste Special -> JSON as class.
Using Json.NET
In the previous samples, we saw how using the DataContractJsonSerializer class for
interacting with REST services is not very straightforward. Despite the fact that we are working
with strings, we always need to convert them into a MemoryStream object in order to perform all
of the required operations.
We can simplify our code by introducing Json.NET, a popular third-party library that is able to
handle serialization in a better way by offering simple methods and better performance. In
addition, it offers a powerful language called LINQ to JSON in order to perform complex
operations on the JSON data. Json.NET is available as a NuGet package here while the official
website (found here) hosts the documentation.
Lets take a look at how (thanks to Json.NET) we are able to simplify the two previous code
samples in order to interact with a REST service. Lets start with the download sample:
private async void OnConsumeServiceClicked(object sender, RoutedEventArgs e)
{
HttpClient client = new HttpClient();
string result = await client.GetStringAsync(new
Uri("http://wp8test.azurewebsites.net/api/values", UriKind.Absolute));
List<Person> people = JsonConvert.DeserializeObject<List<Person>>(result);
}
The deserialization procedure is performed by using the JsonConvert class, which offers a
DeserializeObject<T>() method where T is the type of data we expect in return. As input, it
simply requires the JSON string we just downloaded from the service by using the HttpClient
class.
Here is the reverse process to send some data to the service:
private async void OnConsumeServiceClicked(object sender, RoutedEventArgs e)
{
Person person = new Person
{
Name = "Matteo",
Surname = "Pagani"
};
string result = JsonConvert.SerializeObject(person);
54
Again, the operation is performed by using the JsonConvert class. However, in this case, we
use the SerializeObject() method, which requires as parameter the object to convert and it
simply returns the serialized string. Now we can continue the execution as we did in the
previous sample. We encapsulate the JSON string into a HttpStringContent object and we
send it by using the PostAsync() method of the HttpClient class.
Thanks to the JsonProperty attribute that has been applied to every property, we have been
able to manually define which name to use when the property is translated into a JSON file. The
previous example is common in real apps since C# uses a different notation than the JSON
one. In C#, properties usually start with an uppercase letter while in JSON they start with a
lowercase letter.
Another way to control the serialization is by using a class called JsonSerializerSettings,
which offers many settings such as how dates, errors or numbers should be managed. The
following sample shows another common scenario, which is null values management:
private async void OnConsumeServiceClicked(object sender, RoutedEventArgs e)
{
Person person = new Person
{
55
Name = "Matteo",
Surname = "Pagani"
};
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.NullValueHandling = NullValueHandling.Ignore;
string result = JsonConvert.SerializeObject(person, settings);
IHttpContent content = new HttpStringContent(result);
HttpClient client = new HttpClient();
await client.PostAsync(new Uri("http://wp8test.azurewebsites.net/api/value",
UriKind.Absolute), content);
}
By default, when Json.NET tries to serialize a property with a null value, it includes it anyway
in the JSON file and sets it to null. However, some services do not manage this approach well.
If they find some null properties in the JSON, they raise an error. Thanks to the
JsonSerializerSettings class, we are able to tell Json.NET not to include in the JSON the
empty properties. We do so by setting the NullValueHandling property to
NullValueHandling.Ignore. As you can see, the SerializeObject() method of the
JsonConvert class can accept a second parameter, which is the JsonSerializerSettings
object we previously defined.
LINQ to JSON
When you download a JSON response from a service and you want to convert it into objects
that can be manipulated in code, the deserialization process is helpful since it takes care of
automatically performing the conversion. However, sometimes it is not our scenario. For
example, we could download a complex JSON but we would need to extract just some of its
properties. In this case, Json.NET offers a powerful language called LINQ to JSON, which we
can use to perform LINQ queries on a JSON file so that we retrieve only the data we need.
To perform such operations, we need to use the JObject class, which offers the Parse()
method that is able to convert a plain JSON string in a complex structure that we can explore,
like in the following sample:
private async void OnParseJson(object sender, RoutedEventArgs e)
{
HttpClient client = new HttpClient();
string result = await client.GetStringAsync(new
Uri("http://wp8test.azurewebsites.net/api/values", UriKind.Absolute));
JObject json = JObject.Parse(result);
}
Now, lets see what the most common operations are that we can perform by using the JObject
class.
56
Simple JSON
Lets assume that you have a simple JSON string, like the following one:
{
"Id":1,
"Name":"Matteo",
"Surname":"Pagani"
}
In this case, the JObject class behaves like a Dictionary<string, object> collection, so we
can retrieve the properties simply by referring to them with their name, like in the following
sample:
private async void OnParseJson(object sender, RoutedEventArgs e)
{
HttpClient client = new HttpClient();
string result = await client.GetStringAsync(new
Uri("http://wp8test.azurewebsites.net/api/values/1", UriKind.Absolute));
JObject json = JObject.Parse(result);
string value = json["Name"].Value<string>();
}
To extract the value of the property, we use the Value<T>() method where T is the propertys
type. This way, the value is automatically converted to the proper type (to a string, in this
sample).
Complex JSON
Similar to C#, a JSON string can also contain complex objects, where a property is represented
by another object, like in the following sample:
{
"Id":1,
"Name":"Matteo",
"Surname":"Pagani",
"Address":{
"Street":"Fake address",
"City":"Milan"
}
}
57
Address is a complex property since it contains other subproperties such as Street and City.
To access to these properties, we need to use the SelectToken() method, which requires as
parameter the full JSON path. The following sample shows how to extract the value of the City
property:
private async void OnParseJson(object sender, RoutedEventArgs e)
{
HttpClient client = new HttpClient();
string result = await client.GetStringAsync(new
Uri("http://wp8test.azurewebsites.net/api/values/1", UriKind.Absolute));
JObject json = JObject.Parse(result);
string city = json.SelectToken("Address.City").Value<string>();
}
Collections
As we saw in previous samples in this chapter, JSON can also be used to store collections of
items. In this case, we can use the Children() method of the JObject class to return all of the
items that belong to the collection. The following sample shows how to create a subcollection
that contains only the value of the Name property for each item:
private async void OnGetDataClicked(object sender, RoutedEventArgs e)
{
HttpClient client = new HttpClient();
string result = await client.GetStringAsync(new
Uri("http://wp8test.azurewebsites.net/api/values", UriKind.Absolute));
JObject json = JObject.Parse(result);
List<string> list = json.Children().Select(x =>
x["Name"].Value<string>()).ToList();
}
58
Theoretically, the knowledge acquired in this chapter should be enough to properly manage a
RSS feed. Similar to the JSON data we previously saw, we could simply download the RSS
content by using the HttpClient class and convert it into objects by using the
DataContractSerializer class or LINQ to XML. However, the Windows Runtime includes a
set of classes that are able to do this operation for us. We will just have to provide the URL of
the RSS feed in order to get in return a collection of objects with all of the feed items.
The following sample shows how to perform some basic operations:
private async void OnDownloadFeedClicked(object sender, RoutedEventArgs e)
{
SyndicationClient client = new SyndicationClient();
SyndicationFeed feed = await client.RetrieveFeedAsync(new
Uri("http://feeds.feedburner.com/qmatteoq_eng", UriKind.Absolute));
Title.Text = feed.Title.Text;
Description.Text = feed.Subtitle.Text;
NumberOfItems.Text = feed.Items.Count.ToString();
}
59
The download operation is performed by using the SyndicationClient class, which offers a
method called RetrieveFeedAsync() that simply requires as parameter the RSS feeds URL.
What we get in return is a SyndicationFeed object, which contains a set of properties that are
mapped with the data stored in the XML file. In the previous sample, we extract the title, the
subtitle, and number of items, and we display them to the user by using a set of TextBlock
controls.
All of the items that are included in the feed are stored in a collection called Items. Each item is
represented by the SyndicationItem class, which offers a set of properties that map the XML
ones. The following sample shows how to retrieve the first news of the feed and display to the
user its title and summary (stored in the Title and Summary properties):
private async void OnDownloadFeedClicked(object sender, RoutedEventArgs e)
{
SyndicationClient client = new SyndicationClient();
SyndicationFeed feed = await client.RetrieveFeedAsync(new
Uri("http://feeds.feedburner.com/qmatteoq_eng", UriKind.Absolute));
if (feed.Items.Count > 0)
{
SyndicationItem item = feed.Items.FirstOrDefault();
FirstNewsTitle.Text = item.Title;
FirstNewsSummary.Text = item.Summary;
}
}
60
Note: To use the geolocation service, you need to enable the Location capability in the
manifest file.
The main class we are going to use to interact with the geolocation services is called
Geolocator, which is part of the Windows.Devices.Geolocation namespace.
61
system";
}
else if (accessInformation.CurrentStatus == DeviceAccessStatus.DeniedByUser)
{
Error.Text = "The geolocation services have been disabled for the app ";
}
}
}
As you can see, the usage of the Geolocator class (we will explain later about how to use it) is
encapsulated inside a try / catch statement. In case we get an exception, we can use the
DeviceAccessInformation class to determine the cause. In fact, the services could have been
blocked for the entire OS (we get the value DeniedBySystem) or just for the current app (we get
the value DeniedByUser). The first scenario is managed by the Windows settings; the user can
choose to disable the geolocation services for all of the apps. The second scenario is directly
managed by the app. The first time we are going to use the Geolocator class, a warning
message will be displayed to the user, asking them for confirmation to proceed.
In Windows Phone, we have a way to determine the status of the services before performing
any operation; we just need to check the LocationStatus property of the Geolocator class.
The PositionStatus enumerator will help us to understand the status. If its value is Disabled,
it means that the user has disabled the service in the phones settings. When the services are
active and ready to be used, we will get the Ready value. The Geolocator class also offers an
event called StatusChanged, which we can subscribe if we want to be immediately notified
every time the status of the services changes.
private void OnGetPositionClicked(object sender, RoutedEventArgs e)
{
Geolocator geolocator = new Geolocator();
if (geolocator.LocationStatus == PositionStatus.Disabled)
{
Error.Text = "Geolocation services are disabled";
}
}
62
Another approach is to subscribe to an event called PositionChanged. This way, we can track
the users position and be notified every time he or she moves away from his or her previous
position. We can customize the frequency of the notification with two parameters:
MovementThresold (which is the distance, in meters, that should be travelled from the previous
point) and ReportInterval (which is the timeframe, in milliseconds, that should pass before
one notification and the next). Here is a sample code to continuously track the users position:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Geolocator geolocator = new Geolocator();
geolocator.MovementThreshold = 100;
geolocator.ReportInterval = 1000;
geolocator.PositionChanged += geolocator_PositionChanged;
}
async void geolocator_PositionChanged(Geolocator sender, PositionChangedEventArgs
args)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Geoposition currentPosition = args.Position;
Latitude.Text =
currentPosition.Coordinate.Point.Position.Latitude.ToString();
Longitude.Text =
currentPosition.Coordinate.Point.Position.Longitude.ToString();
});
}
The event handler that we subscribed to the PositionChanged event contains a parameter,
which has a property called Position. It is a Geoposition object, which includes the
coordinates of the current users position inside the Coordinate.Point.Position property.
Please note that we are using the Dispatcher to display these information in the page; it is
required since the PositionChanged event is managed in a background thread.
63
The Windows Phone emulator offers a more advanced tool, which provides a visual map where
you can place one or more pushpins. Each pushpins coordinates will be sent to the emulator
and detected by the Geolocator class. In addition, the tool also offers a way to create a route
between multiple pushpin so that you can simulate the users travel by car, by bike or on foot.
Routes can also be saved as XML files so that you can load them even after you have closed
the emulator.
Geofencing
Geofencing is one of the new features related to the geolocation services that have been added
in Windows 8.1 and Windows Phone 8.1. Thanks to this feature, you will be able to define one
or more circular areas on the map (which are called geofences). When the user enters or exits
the area, your app will be notified even if it is not running at that moment (thanks to the
background tasks that we will introduce in Chapter 11). There are many scenarios in which
geofencing can be useful. For example, an app connected to a store brand could notify the user
to some special promotions every time he or she walks near one of their shops.
An app can create up to 20,000 geofences, even if it is suggested not to exceed the 1,000 quota
(otherwise they could become hard to manage). Every geofence is identified by the Geofence
class, which is part of the Windows.Devices.Geolocation.Geofencing namespace. When
you create a new Geofence object, you need to set the following parameters:
The geofence id, which is a string that univocally identifies the geofence; an app cant
create two geofences with the same id
The geofence area, by using a Geocircle object, which is represented by a pair of
coordinates (latitude and longitude) and the size of the area. (It is suggested not to
create geofences with an area smaller than 100 meters since they could be hard to track
by the device.)
Which events should trigger the notification, by using one of the values of the
MonitoredGeofenceStates enumeration; the possible states are Entered (the user
entered the area), Exited (the user left the area), and Removed (the geofence has been
removed from the system)
A boolean value, which is used to define whether or not it is a one-time geofence; in
case the value is true, the geofence will automatically be removed from the system
once the user has entered or exited the area
A time interval (called a dwell time) which must be spent inside or outside the area so
that the notification is triggered
A start date and time; if it is not specified, the geofence will immediately be activated
A time interval after the start date; if it is specified, the geofence will automatically be
removed from the system when this interval has passed
When the Geofence object has been properly configured, you can add it to the queue by using
the GeofenceMonitor class, which has a unique instance (stored in the Current property) that
is shared among all of the apps. Here is a complete geofencing sample:
private void OnSetGeofenceClicked(object sender, RoutedEventArgs e)
{
string id = "geofenceId";
64
It is placed in a specific position of the map (defined with a Geoposition object) and it
has a radius of 500 meters
We register to monitor all of the available states, so we will be notified every time the
user enters or exits the area or every time the geofence is removed from the system
It is a one-time geofence
The dwell time is set to two seconds; the notification will be triggered only if the user
stays or leaves the area for more than two seconds
To add the Geofence to the system, we call the Add() method on the Geofences collection
offered by the GeofenceMonitor instance.
65
switch (state)
{
case GeofenceState.Entered:
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
MessageDialog dialog = new MessageDialog("The user entered into
the area");
await dialog.ShowAsync();
});
break;
case GeofenceState.Exited:
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
MessageDialog dialog = new MessageDialog("The user has left the
area");
await dialog.ShowAsync();
});
break;
case GeofenceState.Removed:
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
MessageDialog dialog = new MessageDialog("The geofence has been
removed");
await dialog.ShowAsync();
});
break;
}
}
}
For each report stored in the collection returned by ReadReports() method, we check the value
of the NewState property. In the previous sample, we manage all of the possible states, which
are represented by the GeofenceState enumerator, by using a switch statement such as
Entered, Exited or Removed. It is up to you to perform the operation that best fits your
scenario. For example, you could send a notification or display a specific page to the user. In
the sample, we just displayed a pop-up message to the user with a description of what has
happened. Please note that the GeofenceStateChanged event is managed in a background
thread; we need to use the Dispatcher if we want to interact with the controls placed in the
page.
The code we have just seen can also be used in a background task, which is a special class
that contains some code that can also be performed when the app is not running. This way, we
will always be able to detect the geofences activation, even if the user is not using our app. You
will see in detail how to create such a background task in Chapter 11.
66
67
After completing the first step, you will get access to a section called Map services, which will
allow you to get two tokens. The first one is called Map service ApplicationID and it needs to
be inserted in the manifest file. However, this information cant be changed by using the visual
editor. Rather, you will need to right-click the Package.appxmanifest file in Visual Studio and
choose View Code. You will get access to the XML definition of the manifest file. At the
beginning of the file, you will find the following definition:
<mp:PhoneIdentity PhoneProductId="89d51a2c-cdb4-49ee-959d-83f8db75d8f3"
PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
You will need to replace the existing value of the PhoneProductId attribute with the Map
service ApplicationID provided by the portal.
The second token is called Map service Authentication Token and it is used directly in the
apps code. Its first purpose is to register the MapControl so that you can remove the warning
message. For this purpose, you will have to set this token to the MapServiceToken property of
the control, like in the following sample:
<maps:MapControl MapServiceToken="K38JSFqmSBMVu0iTXR4aEg" />
The other purpose is to get access to the services offered by the Windows Runtime APIs that
belong to the Windows.Services.Maps namespace. In this scenario, you will have to assign
the token to the ServiceToken property of the MapService class when the page is loaded:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
MapService.ServiceToken = "K38JSFqmSBMVu0iTXR4aEg";
}
68
Displaying a Pushpin
The MapControl class supports a way to display one or more pushpins on the map by using the
MapIcon class, which represents a base pushpin rendered with a name and an image. A
MapIcon object is identified by two properties:
By default, the MapIcon class uses a default image to render the pushpin. However, you can
customize it by using the Image property, which requires a reference to the images stream
(identified by the RandomAccessStreamReference class). The following sample shows how to
create a pushpin and place it on the map at the current position:
private async void OnAddPushpinClicked(object sender, RoutedEventArgs e)
{
Geolocator locator = new Geolocator();
Geoposition geoposition = await locator.GetGeopositionAsync();
MapIcon icon = new MapIcon();
icon.Location = geoposition.Coordinate.Point;
icon.Title = "Your position";
icon.Image = RandomAccessStreamReference.CreateFromUri(new Uri("msappx:///Assets/logo.jpg"));
MyMap.MapElements.Add(icon);
}
The pushpin created in the previous sample has a custom title and icon, which is an image
included in the Assets folder of the Visual Studio project. The MapControl class includes a
collection called MapElements, with all of the elements that should be displayed in overlay over
the map. To display the pushpin, we just need to add it to the collection by using the Add()
method.
69
However, there is another way to create a set of pushpins, which offers more flexibility to the
developer. Instead of displaying just an image, we will be able to define a custom layout by
using XAML and manage the users interaction (such as tapping on the pushpin). The approach
is similar to the one we learned in Chapter 3 about displaying a collection with a ListView or a
GridView control. We are going to define the template of a single pushpin and then we will
connect a collection of pushpins to the map. Each pushpin will be rendered by using the
specified template.
We can do this by using the MapItemsControl class, like in the following sample:
<maps:MapControl x:Name="MyMap">
<maps:MapItemsControl x:Name="Pushpins">
<maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="CornflowerBlue" Tapped="OnPushpinClicked">
<TextBlock Text="{Binding Name}" Foreground="Black"
maps:MapControl.Location="{Binding Location}"
Style="{StaticResource TitleTextBlockStyle}" />
</Border>
</DataTemplate>
</maps:MapItemsControl.ItemTemplate>
</maps:MapItemsControl>
</maps:MapControl>
The last step is to define a collection of PushPin objects and assign it to the ItemsSource
property of the MapItemsControl class, in the same way we would do with a ListView control:
private void OnAddPushpinsClicked(object sender, RoutedEventArgs e)
{
BasicGeoposition position1 = new BasicGeoposition();
position1.Latitude = 45.8080;
position1.Longitude = 9.0800;
Geopoint geopoint1 = new Geopoint(position1);
70
However, if you take a look at the ItemTemplate we defined in XAML, you will notice that we
subscribed to the Tapped event of the Border control. The purpose is to manage the users
interaction so that, when the user taps on a pushpin, we can display some additional
information. The following sample shows how to manage this event in order to display a pop-up
message with the name of the selected location:
private async void OnPushpinClicked(object sender, TappedRoutedEventArgs e)
{
Border border = sender as Border;
PushPin selectedPushpin = border.DataContext as PushPin;
MessageDialog dialog = new MessageDialog(selectedPushpin.Name);
await dialog.ShowAsync();
}
The Tapped event (like any other event) provides a parameter called sender, which is the
XAML control that triggered the event. Thanks to this parameter, we are able to get a reference
to the Border control and to access its DataContext, which is the Pushpin object that has
been selected. This way, it is easy to perform a cast and retrieve the value of the Name property
so that we can display it to the user.
71
Using the MapRouteFinder class is one of the scenarios that requires a valid map token.
Consequently, the first thing to do is to properly set the ServiceToken property of the
MapService class.
72
As you can see, the GetDrivingRouteAsync() method does not support just setting a starting
and an ending point but also some parameters in order to customize the route calculation. In the
previous sample, we chose the kind of optimization by using the MapRouteOptimization
enumerator (in this case, it will calculate the shortest route based on time) and specified that we
wanted to avoid highways (by using the MapRouteRestrictions enumerator). The method will
return a MapRouteFinderResult object, which offers many properties with the details of the
calculated route. In the previous sample, we displayed to the user the duration (stored in the
EstimatedDuration property) and the length of the route (store in the LenghInMeters
property). One important property is called Legs, which is a collection of all of the routes that
have been calculated. Every route offers a collection called Menuevers, which is the list of all of
the instruction to go from point A to point B. In the sample, we store in a collection all of the
values of the InstructionText property, which contains the textual indications (like, for
example, At the end of the street go left.).
In addition, we can use the MapRouteFinder class not just to retrieve the information about the
route but also to display it on the map by using the MapRouteView class and the Routes
collection offered by the MapControl class. Lets take a look at the following sample:
private async void OnShowRouteClicked(object sender, RoutedEventArgs e)
{
MapService.ServiceToken = "K38JSFqmSBMVu0iTXR4aEg";
BasicGeoposition position1 = new BasicGeoposition();
position1.Latitude = 45.8080;
position1.Longitude = 9.0800;
Geopoint startPoint = new Geopoint(position1);
BasicGeoposition position2 = new BasicGeoposition();
position2.Latitude = 45.4608;
position2.Longitude = 9.1763;
Geopoint endPoint = new Geopoint(position2);
MapRouteFinderResult result = await
MapRouteFinder.GetDrivingRouteAsync(startPoint, endPoint, MapRouteOptimization.Time,
MapRouteRestrictions.Highways);
if (result.Status == MapRouteFinderStatus.Success)
{
MapRouteView routeView = new MapRouteView(result.Route);
routeView.RouteColor = Colors.Red;
routeView.OutlineColor = Colors.Black;
MyMap.Routes.Add(routeView);
}
}
73
The MapRouteView class takes care of encapsulating the Route property of the
MapRouteFinderResult class. In addition, we can use it to customize the visual layout of the
route by setting the route color (RouteColor) and the color of the border applied to the route
(OutlineColor). In the end, you just need to add the newly created MapRouteView object to
the Routes collection of the MapControl object you have placed in the page.
Optionally, you can also pass a second parameter to the FindLocationsAsync() method,
which is a Geopoint object with the coordinates of the approximate area where the location is
placed. If you do not have this information, you can simply pass null as value, as we did in the
previous sample. The method returns a MapLocationFinderResult object, which contains two
important pieces of information. The first one is Status, which informs the developer whether or
not the operation has successfully completed. In the previous sample, we move on with the rest
of the code only if we get a Success value. The other piece of information is Locations, which
is a list of all of the locations that have been found for the specified search keyword. For each
location, we have access to a property called Point, with the locations coordinate. In the
previous sample, we take the coordinates of the first location and we display it on the map by
calling the TrySetViewAsync() method.
74
In this sample, we retrieve the users position and we display, with a pop-up message, the
corresponding address, retrieved by combining the values of the Street and Town properties of
the Address class.
75
Now you are ready to add the control to your page. It is simply called Map and it is included in
the Bing.Maps namespace:
<Page
x:Class="SampleMap.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:maps="using:Bing.Maps"
mc:Ignorable="d">
<Grid>
<maps:Map x:Name="MyMap" />
</Grid>
</Page>
76
Unfortunately, as you can see, in this case we need to work with a different class that identifies
a position than the one used by the Geolocator class. In fact, the GetGeopositionAsync()
method returns a Geoposition object while the SetView() method requires a Location object.
Consequently, we need to manually convert from one type to the other before we can interact
with the map.
Displaying a Pushpin
The Map control provides, in the Bing.Maps namespace, a specific class in which to manage
pushpins called Pushpin. However, you cant customize the icon by providing another image,
Instead, you will need to customize the XAML template. You have the chance anyway to
customize the text, which will be displayed in overlay over the pushpin. The following sample
shows how to display a pushpin over the map:
private void OnFindPositionClicked(object sender, RoutedEventArgs e)
{
Location position1 = new Location(45.8080, 009.0800);
Pushpin pin = new Pushpin();
pin.Text = "Pub";
pin.Tapped += pin_Tapped;
MapLayer.SetPosition(pin, position1);
MyMap.Children.Add(pin);
}
We defined a Pushpin object by setting the Text property with the name to display. In addition,
we subscribed to the Tapped event, which is triggered when the user taps on it. In the previous
sample, we displayed to the user (using a pop-up message) the name of the selected pushpin.
We are able to do it thanks to the parameter called sender, which is the Pushpin object that
triggered the event.
The approach to add the pushpin to the map is different than the one we saw with Windows
Phone. We need to call the SetPosition() method of the MapLayer class, passing as
parameters the Pushpin object and the Location object that identifies the position. However,
this line of code just takes care of setting the pushpins position. To effectively display it on the
map, we will need to add it to the Children collection of the Map control, which contains all of
the objects that are displayed in overlay.
77
However, we can also use another approach that is similar to the one we saw with the Windows
Phone control. We can create a collection of pushpins and then define a template, which will be
used to render each pushpin, in the same way we do with a ListView or GridView control to
display a list of items. To achieve this goal, we need to use the MapItemsControl object, which
offers a property called ItemTemplate to customize the look and feel of the pushpin:
<maps:Map x:Name="MyMap">
<maps:MapItemsControl x:Name="Pushpins">
<maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<maps:Pushpin Tapped="pin_Tapped" Text="{Binding Name}">
<maps:MapLayer.Position>
<maps:Location Latitude="{Binding Latitude}"
Longitude="{Binding Longitude}" />
</maps:MapLayer.Position>
</maps:Pushpin>
</DataTemplate>
</maps:MapItemsControl.ItemTemplate>
</maps:MapItemsControl>
</maps:Map>
To define the pushpins position, we use a special attached property called Location, offered
by the MapLayer class. This property has two attributes, Latitude and Longitude, which
define the pushpins coordinates. In the same way we did with Windows Phone, we need a
custom class that defines a pushpin and which we will use to populate the MapItemsControl
object, like in the following sample:
public class MyPushpin
{
public string Name { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
}
Now you can simply create your collection of MyPushpin objects and assign it to the
ItemsSource property of the MapItemsControl object. The following sample shows how to
display two pushpins on the map by using the template we previously defined:
private void OnAddPushpinsClicked(object sender, RoutedEventArgs e)
{
List<MyPushpin> pushpins = new List<MyPushpin>();
MyPushpin pin1 = new MyPushpin
{
Name = "Bar",
Latitude = 45.8080,
Longitude = 9.0800
78
};
MyPushpin pin2 = new MyPushpin
{
Name = "Banca",
Latitude = 45.8052,
Longitude = 9.0825
};
pushpins.Add(pin1);
pushpins.Add(pin2);
Pushpins.ItemsSource = pushpins;
}
79
The reverse geocoding operation (which means converting a set of coordinates into a civic
address) is performed in a similar way. The main difference is, in this case, we are going to use
a ReverseGeocodeRequestOptions object, which requires as parameter the Location object
with the coordinates of the place we want to resolve:
private async void OnReverseGeocodeClicked(object sender, RoutedEventArgs e)
{
Geolocator geolocator = new Geolocator();
Geoposition position = await geolocator.GetGeopositionAsync();
Location location = new Location(position.Coordinate.Point.Position.Latitude,
position.
Coordinate.Point.Position.Longitude);
SearchManager manager = MyMap.SearchManager;
ReverseGeocodeRequestOptions reverseGeocodeRequest = new
ReverseGeocodeRequestOptions
(location);
LocationDataResponse response = await
manager.ReverseGeocodeAsync(reverseGeocodeRequest);
if (!response.HasError)
{
GeocodeAddress address = response.LocationData.FirstOrDefault().Address;
MessageDialog dialog = new MessageDialog(address.FormattedAddress);
await dialog.ShowAsync();
}
}
The previous sample shows how to retrieve the address of the currents user position, obtained
by using the Geolocator class. In this case, since we already know the places coordinates, we
can use the FormattedAddress property (which is included in every object that belongs to the
LocationData collection) in order to get the civic address.
The Sensors
Most of the tablets and smartphones on the market include a set of sensors which can be used
to detect if and how the device is moving in space. Many games use this approach in order to
provide an alternative way to control the game (instead of relying on virtual joy pads that are
displayed on the screen).
Windows and Windows Phone devices offer many sensors; each of them is represented by one
of the classes that is included in the Windows.Devices.Sensors namespace. Here are the
main ones:
80
Inclinometer: Identified by the Inclinometer class; it measures roll, pitch, and yaw
Compass: Identified by the Compass class; it measures the phones position compared
to the magnetic north
Magnetometer: Identified by the Magnetometer class; it measures the magnetic fields
intensity
Gyrometer: Identified by the Gyrometer class; it measures the angular speed of the
device
However, in most cases, what is important for the developer is to discover the position of the
device with the best accuracy. For this purpose, the Windows Runtime offers a special class
called OrientationSensor, which is able to combine all of the information retrieved by the
available sensors to provide a set of optimized values. It does so by automatically excluding all
of the dirty values that could ruin the user experience (UX). This class, in order to work
properly, requires that the device includes, at least, an accelerometer and a compass. However,
the best results are achieved if the device also includes a gyroscope. Consequently, if your app
heavily relies on these sensors in order to work properly, it is better to properly configure them
in the Requirements section of the Windows Phone manifest file. This way, if the user has a
device that does not satisfy these requirements, he or she will not be able to install the app at all
from the Store.
No matter which sensor we use, they all work in the same way. Every class offers a method
called GetDefault(), which retrieves a reference to the sensors. It is important to always check
that the value is not null before performing additional operations. In fact, the device where the
app is running could not offer that sensor; consequently, any other interaction would return an
exception. After you have a valid reference to the sensor, you can use two different approaches,
which are similar to what we saw with the GPS tracking.
The first approach is to ask for a single reading by using the GetCurrentReading() method.
The data type you will get in return can change from sensor to sensor. Typically, the name of
the class matches the name of the sensors plus the Reading suffix (for example, the
Accelerometer class returns an AccelerometerReading object). The following sample shows
how to use the Accelerometer class to get a single reading of the current position:
private void OnStartAccelerometerClicked(object sender, RoutedEventArgs e)
{
Accelerometer accelerometer = Accelerometer.GetDefault();
if (accelerometer != null)
{
AccelerometerReading reading = accelerometer.GetCurrentReading();
X.Text = reading.AccelerationX.ToString();
Y.Text = reading.AccelerationY.ToString();
Z.Text = reading.AccelerationZ.ToString();
}
}
The position of the device on the three axes (stored in the AccelerationX, AccelerationY,
and AccelerationZ properties of the reading object) are displayed on the page by using three
TextBlock controls.
81
The other approach is to continuously monitor the sensors so that you will be notified every time
the phone is moved into a new position. In this scenario, we can subscribe to an event, exposed
by all of the sensors classes, called ReadingChanged. The following sample shows how to
subscribe to this event for the Accelerometer class so that we can continuously update the
page with the current coordinates:
private void OnStartAccelerometerClicked(object sender, RoutedEventArgs e)
{
Accelerometer accelerometer = Accelerometer.GetDefault();
if (accelerometer != null)
{
accelerometer.ReadingChanged += accelerometer_ReadingChanged;
}
}
void accelerometer_ReadingChanged(Accelerometer sender,
AccelerometerReadingChangedEventArgs args)
{
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
X.Text = args.Reading.AccelerationX.ToString();
Y.Text = args.Reading.AccelerationY.ToString();
Z.Text = args.Reading.AccelerationZ.ToString();
});
}
82
Source contracts, which are implemented by apps that want to share some data
Target contracts, which are implemented by apps that want to receive shared data
The most important feature about contracts is that they are completely transparent to the
developer. The source app does not have to know anything about the target that will receive the
data. In the same way, the target app is able to receive the shared data without knowing
anything about the source app.
83
The source app registers itself to be notified every time the user chooses to share some data.
Inside this event handler, we are going to define the content to share. Then, it will be time for
the OS to take care of the next step of the operation. It will display a list containing all of the
apps that have registered the share target contract and which are able to support the data type
we are sharing. The last step is to perform the real sharing operation, which is performed by the
target app. It will receive the shared data and it will take care of processing it. In the rest of the
chapter, we will look, in detail, at how to implement both the source and target sharing
contracts.
However, this code also works on Windows. Invoking the ShowShareUI() method will achieve
the same result as pressing the Share button in the Charms bar.
Sharing Content
The sharing operation is performed at page level since every page can share different kinds of
content. For this reason, each page has its own instance of the DataTransferManager class,
which is retrieved by using the GetForCurrentView() method. Since, in Windows, the sharing
operation can be triggered outside the app (by using the Charm bar), the
DataTransferManager class offers a specific event called DataRequest, which is triggered
every time a sharing action is started. It is inside this event handler that we are going to define
the content to share, like in the following sample:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataTransferManager transferManager =
DataTransferManager.GetForCurrentView();
transferManager.DataRequested += transferManager_DataRequested;
84
}
private void transferManager_DataRequested(DataTransferManager sender,
DataRequestedEventArgs args)
{
//manage the sharing operation
}
}
85
After you have defined title and description, it is time to define the real content. The
Request.Data object offers many methods that begin with the Set prefix. Each of them
represents one of the data types you can share. Lets take a look at some samples of the most
common ones.
Sharing a Text
Text can be shared with the SetText() method, which simply accepts the string to share, like in
the following sample:
private void transferManager_DataRequested(DataTransferManager sender,
DataRequestedEventArgs
args)
{
args.Request.Data.Properties.Title = "Sharing demo";
args.Request.Data.Properties.Description = "This is a sharing demo";
args.Request.Data.SetText("This is a demo text");
}
86
Sharing a Link
A link is simply a string. However, some apps are able to manage it in a special way. For
example, the People app in Windows 8.1 is able to automatically generate a preview of the
website before sharing the link on a social network. The method to use to share a link is called
SetWebLink() and it simply requires, as parameter, a Uniform Resource Identifier (Uri) object
with the address, like in the following sample:
void transferManager_DataRequested(DataTransferManager sender,
DataRequestedEventArgs args)
{
args.Request.Data.Properties.Title = "Sharing demo";
args.Request.Data.Properties.Description = "This is a link to share";
Uri url = new Uri("http://wp.qmatteoq.com", UriKind.Absolute);
args.Request.Data.SetWebLink(url);
}
Sharing a File
Sharing a file is one of the most frequently used features since all of the complex data (such as
images) are treated as files by the OS.
Note: The Request.Data object offers a specific method for sharing images, which is called
SetBitmap(). However, it is useless in Windows Phone since all of the native apps do not
manage it; instead, they treat them as regular files. Consequently, we will not discuss the
use of this method in this book.
However, before showing you how to share a file in an app, there is an important concept to
introduce: asynchronous operations. If you have read Chapter 5 (which is about managing the
local storage). you will know that all of the storage APIs are asynchronous and based on the
async and await pattern. Consequently, in a sharing operation, the OS will not be able to
determine when the files are ready and when the sharing contract can be activated. If you
remember the apps life cycle as explained in Chapter 4, a similar situation occurs when you
need to save the apps state during the suspension event.
To avoid this issue, we are going to use the same mechanism, which is using a deferral object.
In this case, its type is DataRequestDeferral and it is provided by the GetDeferral() method
offered by the Request object. After getting a reference to this object, you will be able to
perform any asynchronous operation. You will just have to remember to call the Complete()
method once the operation is done. Lets take a look at how to use this approach in combination
with the method provided to share one or more files, called SetStorageItems():
async void transferManager_DataRequested(DataTransferManager sender,
DataRequestedEventArgs args)
{
args.Request.Data.Properties.Title = "Sharing demo";
args.Request.Data.Properties.Description = "This is a file sharing demo";
DataRequestDeferral deferral = args.Request.GetDeferral();
87
The first thing we do is get a reference to the DataRequestDeferral object by using the
GetDeferral() method offered by the Request object (which is one of the properties of the
methods parameters). Then, we retrieve a reference to the file (or the files) we want to share. In
the previous file, we want to share a text file called text.txt stored in the local storage. To
perform the sharing, we call the SetStorageItems() method of the Request.Data object,
passing as parameter the list of files to share. This method always requires a collection so we
always need to create a list (even if, as in the previous sample, we are going to share just one
file). In the end, we complete the asynchronous operation by calling the Complete() method
exposed by the DataRequestDeferral object.
The data type (or types) we want to support, which can be:
o Text
o URI
o HTML
o StorageItems (for files)
In case we want to be able to receive files, we need to also specify which types we are
going to support by setting up the section titled Supported file types. (We will need to
press the Add new button for each type we want to support and to specify the files
extension. Otherwise, we can simply enable the Supports any file type option to be
able to receive any files type)
The next step is to manage the sharing operation. Typically, when our app is chosen as a share
target, a specific page of the app is opened. It will provide a preview of the content that will be
shared and will allow the user to confirm or cancel the operation. Consequently, you will not find
any sample about how to define the sharing pages layout. It is up to you and to the content you
want to be able to share to define the layout that fits best your needs. For example, if your app
allows the user to share an image, the sharing page could display a thumbnail. Or, if your app
can receive a link, it could display a preview of the website.
88
When a target app is activated by a share contract, it does not follow the traditional apps life
cycle. Consequently, the OnLaunched() method of the App class is not triggered as it is when
the app is opened by using the main tile. In a sharing scenario, the app is activated by invoking
the OnShareTargetActivated() method which can be declared in the App class, like in the
following sample:
protected override void OnShareTargetActivated(ShareTargetActivatedEventArgs args)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
rootFrame = new Frame();
}
rootFrame.Navigate(typeof(SharePage), args.ShareOperation);
Window.Current.Content = rootFrame;
Window.Current.Activate();
}
This method simply takes care of redirecting the user to the page we prepared to manage the
sharing operation (in the previous sample, it is called SharePage). By using the Navigate()
method of the Frame class, we also add, as navigation parameter, the ShareOperation
property that is included in the OnShareTargetActivated() methods parameters. This
property contains all of the information about the sharing operation (such as the data that has
been shared). Consequently, we will need to use it in the sharing page in order to perform all of
the operations. The only thing to highlight in the previous code is that, before performing the
navigation, we check whether or not the Frame already exists, otherwise we create it. This way,
we can properly manage the apps life cycle. In fact, the app could also be activated when it was
suspended in memory.
Lets take a look, in detail, at how to receive the most common data types from another app.
Receiving a Text
No matter what data type we want to receive, we will need to manage the OnNavigatedTo()
method (as we learned about in Chapter 4 about navigation). Thanks to this method, we are
able to retrieve the parameter that has been passed by the previous page. This parameter
contains a ShareOperation object, which offers a property called Data. It contains all of the
information about the content that has been shared. Lets take a look at the following sample:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
ShareOperation shareOperation = e.Parameter as ShareOperation;
if (shareOperation.Data.Contains(StandardDataFormats.Text))
{
string text = await shareOperation.Data.GetTextAsync();
MessageDialog dialog = new MessageDialog(text);
await dialog.ShowAsync();
}
89
The first operation is to understand what kind of content has been shared. In fact, a target app
could implement many sharing contracts. Consequently, we need to detect which data type we
have received so that we can properly manage it. This operation is performed by checking the
content of the Data property. The Windows Runtime offers an enumerator called
StandardDataFormats, which offers a value for each supported data type. By using the
Contains() method, we are able to check if the Data property contains a specific data type. In
the previous sample, we check for the Text data type. After we have identified the proper data
type, the Data object offers many methods (which start with the Get prefix) that are able to
parse the content and convert it into the proper type. In the previous sample, since we are
working with text, we call the GetTextAsync() method, which simply returns the string that has
been shared by the source app. In the sample, we display it to the user using a pop-up
message.
However, the sharing operation is not completed. This way, we just showed a preview of the
content received by the other app to the user. The last and most important step is to interact
with the shared content. The type of interaction is not fixed but, rather, it depends upon the
apps purpose. For example, a Twitter app will allow to post the received content on the users
timeline whereas a Facebook app will allow to post the received text as a status on the social
network. Consequently, you will not find any specific sample on how to perform the sharing
operation in this chapter. However, there is one step that it is always required regardless of the
way you perform the sharing operation. That step is notifying the OS that the sharing operation
is completed and that the target app can be closed so that control can be returned to the source
app. This notification is performed by calling the ReportCompleted() method on the
ShareOperation object we received as parameter in the OnNavigatedTo() event. Here is a
complete sample:
public sealed partial class SharePage : Page
{
private ShareOperation shareOperation;
public SharePage()
{
this.InitializeComponent();
}
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
shareOperation = e.Parameter as ShareOperation;
if (shareOperation.Data.Contains(StandardDataFormats.Text))
{
string text = await shareOperation.Data.GetTextAsync();
MessageDialog dialog = new MessageDialog(text);
await dialog.ShowAsync();
}
}
90
You will notice that we have defined the ShareOperation object at class level. This way, we
can interact with it both in the OnNavigatedTo() method (to retrieve the shared content) and in
the method that performs the sharing (in this case, it is an event handler that is triggered when
the user presses a button in the page).
Receiving a Link
Now that we have seen how to receive a text, it will be easy to understand how to receive all of
the other data types. In fact, the base approach is always the same. We subscribe to the
OnNavigatedTo() method to receive the ShareOperation object, which contains the shared
content. Then, after the sharing operation is completed, we call the ReportedCompleted()
method. The only difference is that, for each data type, we will need to retrieve the content in a
different way.
When it comes to working with links, we will have to check if the Data property of the
ShareOperation object contains a WebLink content. In this case, we can retrieve it by using
the GetWebLinkAsync() method, like in the following sample:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
ShareOperation shareOperation = e.Parameter as ShareOperation;
if (shareOperation.Data.Contains(StandardDataFormats.WebLink))
{
Uri uri = await shareOperation.Data.GetWebLinkAsync();
MessageDialog dialog = new MessageDialog(uri.AbsoluteUri);
await dialog.ShowAsync();
}
}
Receiving a File
When you receive a file from a source app, the Data object contains a value of type
StorageItems. In this scenario, we can retrieve the list of shared files by using the
GetStorageItemsAsync(), which returns a read-only collection. If you want to perform
additional operations, it is necessary to copy the files in the apps local storage, like in the
following sample:
91
92
</Page>
Figure 11: The Visual Studio designer highlights the area displayed when the page is opened
due to a sharing contract
93
As with any other contract, the apps activation is managed by the App class. Specifically, in this
case, it is done so by a method called OnFileOpenPickerActivated().
protected override void OnFileOpenPickerActivated(FileOpenPickerActivatedEventArgs
args)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
rootFrame = new Frame();
}
rootFrame.Navigate(typeof(OpenPickerPage), args.FileOpenPickerUI);
Window.Current.Content = rootFrame;
Window.Current.Activate();
}
The approach is similar to the one we have seen for the sharing contract. By using the
Navigate() method of the Frame class, we redirect the user to the page we have prepared to
manage the file picker. As navigation parameters, we pass to the page a property called
FileOpenPickerUI which we are going to use to interact with the picker. Consequently, the first
thing we will have do in the picker page is to retrieve, in the OnNavigatedTo() method, a
reference to the FileOpenPickerUI object and store it in a variable defined at class level, like
in the following sample:
public sealed partial class OpenPickerPage : Page
{
private FileOpenPickerUI fileOpenPickerUI;
public OpenPickerPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter != null)
{
fileOpenPickerUI = e.Parameter as FileOpenPickerUI;
}
}
}
94
As previously mentioned, the page will display the list of files that the user can share so that he
or she can pick one of them. Every time the user chooses one of the files, we need to interact
with the FileOpenPickerUI object to communicate to the app that invoked the
FileOpenPicker object which files have been selected. We achieve this goal by calling the
AddFile() method and passing as parameter the selected file, represented by a StorageFile
object. The following sample shows a scenario with a ListView control that displays the list of
available files. When the user taps on one of them, the SelectionChanged event is triggered.
We use it to add the selected file to the FileOpenPickerUI object.
private async void OnFilesSelected(object sender, SelectionChangedEventArgs e)
{
string fileName = Files.SelectedItem.ToString();
StorageFile file = await
ApplicationData.Current.LocalFolder.GetFileAsync(fileName);
fileOpenPickerUI.AddFile(fileName, file);
}
As you can see, the AddFile() method also requires a unique identifier for the file other than
the reference to a StorageFile object.
What happens next is based upon how the FileOpenPicker class was used by the source app.
If the picker has been invoked to select just one file, by calling the AddFile() method the
control will be immediately returned to the original app, which will receive the StorageFile
object we have passed. But, if the picker has been invoked to allow multiple selections, your
app will keep the control so that the user can tap on more items in the list and add more files to
the collection. The OS will automatically add a button to your page to complete the operation.
Only after the user has pressed it will the control be returned to the original app (which will
receive a collection with all of the selected files).
In case you are dealing with multiple selections, the FileOpenPickerUI class also offers a way
to perform additional operations. For example, the user could decide to remove from the list a
file that he or she has previously selected from the list. In this case, it is enough to call the
Remove() method by passing as parameter the file identifier. The following sample shows a
more accurate way to manage the SelectionChanged event of the ListView control. Before
adding the file to the FileOpenPickerUI object, we check to see if it is already on the list by
using the ContainsFile() method. If the answer is yes, instead of adding it, we remove it.
Otherwise, we simply add it as we did in the previous sample:
private async void OnFilesSelected(object sender, SelectionChangedEventArgs e)
{
string fileName = Files.SelectedItem.ToString();
if (fileOpenPickerUI.ContainsFile(fileName))
{
fileOpenPickerUI.RemoveFile(fileName);
}
else
{
StorageFile file = await
95
ApplicationData.Current.LocalFolder.GetFileAsync(fileName);
fileOpenPickerUI.AddFile(fileName, file);
}
}
Supporting Search
Search is one of the topics that has deeply changed in Windows 8.1. In fact, Windows 8 offered
a specific search contract which Windows Store apps were able to implement. This way, we
were able to include results related to our apps data in the native search feature, which is
activated by pressing the Search button in the Charms bar. Consequently, the guidelines stated
that the app should always rely on this contract to provide the search feature. You could not add
a search area in your app; instead, the user had to use the Charms bar to start a search within
the app.
However, many users were not comfortable using the Charms bar, especially on traditional
computers. Since, by default, the Charms bar is hidden, many users thought that many apps did
not implement a search feature. Consequently, Microsoft decided to change the search
guidelines in order to improve discoverability. Now, Windows Store apps are able to include
their own search area. For this reason, the search contract is now deprecated. You will still find
it in the list of available contracts but it has been maintained only for backward compatibility with
existing 8.0 apps that are implementing it.
It is important to highlight that this approach has changed only when it comes to Windows Store
apps for Windows. Windows Phone has never supported the concept of universal search and
does not provide a Charms bar. Consequently, developers have always been able to support
search in the way they prefer. In fact, you will not find the search contract in the manifest file of
a Windows Phone project.
The Windows Runtime includes a set of controls that can be useful to implement search in a
Windows Store app. However, you will not find a universal control but, rather, two different
controls for Windows and Windows Phone.
96
The following sample shows how to include the SearchBox control in your page:
<SearchBox PlaceholderText="Search a person"
SuggestionsRequested="OnSuggestionRequested"
SearchHistoryEnabled="True"
FocusOnKeyboardInput="True"
QuerySubmitted="OnQuerySubmittedClicked" />
With the usual navigation approach, we will be able to retrieve the keyword as navigation
parameter in the destination page (in the previous sample, it is called SearchResultsPage).
The purpose of this page is to query your data with the specified keyword and to display the
results to the user. You will not find any specific sample in this chapter since the type of query to
perform and the layout of the result page depends upon the data that your app is able to
manipulate and how it is structured. For example, this page could trigger a search using a REST
service or perform a search in a local database.
As we saw when we added the SearchBox control in the page, we can subscribe to another
event called SuggestionRequested. It is triggered when the user is typing some text in the box
and we want to provide suggestions based on the apps data. For example, if the SearchBox
control is used to perform a search on a set of customers stored in a local database, we could
suggest to the user the customers whose names matches the one he or she is typing. The
following sample shows how to manage this event handler to provide suggestions:
private void OnSuggestionRequested(SearchBox sender,
97
SearchBoxSuggestionsRequestedEventArgs args)
{
List<Person> list = new List<Person>
{
new Person
{
Name = "Matteo",
Surname = "Pagani"
},
new Person
{
Name = "Ugo",
Surname = "Lattanzi"
},
new Person
{
Name = "Marco",
Surname = "Dal Pino"
}
};
IEnumerable<string> names = list.Select(x => x.Name);
args.Request.SearchSuggestionCollection.AppendQuerySuggestions(names);
}
The event handlers parameter contains a property called Request, which offers many ways to
interact with the SearchBox. Specifically, we are interested in a collection called
SearchSuggestionCollection, which is the list of suggestions that are proposed to the user.
We can add a single word by using the AppendQuerySuggestion() method or, as in the
previous sample, we can add a list of words by using the AppendQuerySuggestions() method.
You will notice that the suggestions are simple strings. Consequently, in the previous sample,
we perform a LINQ query by using the Select() method to retrieve only the values of the Name
property from the collection of Person object we have previously created:
98
Also, this control offers a property called PlaceholderText to set a text that is displayed in the
box until the user starts typing some text. The following sample shows how to use the Button
control we have included in the page to trigger a search:
private void OnSearchClicked(object sender, RoutedEventArgs e)
{
Frame.Navigate(typeof(SearchPage), SearchBox.Text);
}
As you can see, the code is simple. We redirect the user to a specific page (in this sample,
called SearchPage), which will display all of the results according to how our data is structured
inside the app. As navigation parameter, we pass to the destination page the search keyword,
which is stored in the Text property of the control. We will be able to retrieve it, as usual, by
using the OnNavigatedTo() method.
Suggestions in the AutoSuggestBox control are implemented in a different way than the
SearchBox one. Instead of offering a specific event, the approach is similar to the one used by
controls such as ListView or GridView. We are going to set the list of suggestions as the value
of the ItemsSource property of the control, like in the following sample:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
List<Person> list = new List<Person>
{
99
new Person
{
Name = "Matteo",
Surname = "Pagani",
},
new Person
{
Name = "Ugo",
Surname = "Lattanzi",
},
new Person
{
Name = "Marco",
Surname = "Dal Pino",
}
};
IEnumerable<string> names = list.Select(x => x.Name);
SearchBox.ItemsSource = names;
}
However, the AutoCompleteBox control offers a deeper way to customize the drop-down menu
that is displayed with the suggestions when the user starts typing some text. Lets take a look at
the following sample:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
List<Person> list = new List<Person>
{
new Person
{
Name = "Matteo",
Surname = "Pagani",
Role = "Support Engineer"
},
new Person
{
Name = "Ugo",
Surname = "Lattanzi",
Role = "Web Developer"
},
new Person
{
Name = "Marco",
Surname = "Dal Pino",
Role = "Mobile Developer"
}
};
SearchBox.ItemsSource = list;
100
As you can see, we have added a new property to the person class called Role, which contains
the job position of the user. In addition, we did not filter the collection with LINQ to retrieve just
the list of names. We have set, as ItemsSource of the AutoSuggestBox control, the full list of
Person objects.
The AutoSuggestBox control behaves like a ListView or GridView control so we are able to
define the ItemTemplate property with a DataTemplate, which will be used to define the layout
of every single suggestion in the drop-down list. The following sample shows how to define the
ItemTemplate so that the suggestions list displays the users first and last names above their
roles. In this sample, their names will be listed in a bigger font than their roles:
<AutoSuggestBox PlaceholderText="Search a person"
AutoMaximizeSuggestionArea="True"
TextMemberPath="Name"
x:Name="SearchBox">
<AutoSuggestBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Margin="0, 0, 5, 0" />
<TextBlock Text="{Binding Path=Surname}" />
</StackPanel>
<TextBlock Text="{Binding Path=Role}" FontSize="16" />
</StackPanel>
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>
It is important to mention the usage of a property called TextMemberPath. Even if, in this case,
we are displaying a list of suggestions by using a complex object (the Person class), in the end
what the AutoSuggestBox needs is a string, which is inserted in the box when the user taps on
the suggestion. Consequently, when we use complex objects, we need to specify which
property of the object will be used to fill the box. In the previous sample, we have set the
property to Name. This way, when the user will tap on a suggestion, the name of the person will
automatically be inserted in the control.
101
Extensions
Extensions are similar to contracts except that, in this case, the agreement is not established
between two apps but between the app and the OS. Lets take a look at the most important
ones.
File Activation
When an app registers for the file extension activation, it is able to manage a set of specific file
types. Whenever the user tries to open a file whose extension has been registered by our app,
we will be able to intercept the request and process the file. Lets take a look, in detail, at how to
102
support file activation from both ways: the source app (the one that opens the file) and the target
app (the one that receives the file).
File activation, as we have seen with contracts, is managed with a specific activation method in
the App class, which is triggered when the app is opened consequently to a file activation
request. The method is called OnFileActivated() and the following sample shows how to
manage it:
protected override void OnFileActivated(FileActivatedEventArgs args)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
rootFrame = new Frame();
}
rootFrame.Navigate(typeof(ViewFilePage), args.Files[0]);
Window.Current.Content = rootFrame;
Window.Current.Activate();
}
The approach is similar to the one we saw for the sharing contract. We have added to the
project a specific page that will deal with the received file, which is called ViewFilePage. When
the app has been opened due to a file activation, we redirect the user to this page passing, as
navigation parameter, the received file. The file is stored inside a collection called Files, which
is a property of the event handler parameters. However, since a file activation cant occur with
more than one file, it is enough to retrieve the first item of the collection and pass it to the
destination page.
103
Thanks to the navigation system, we are able to retrieve, in the OnNavigatedTo() method of
the destination page, the file, which is represented by the StorageFile class, which is the
common class we saw in Chapter 5 that identifies files in the Windows Runtime. The following
sample shows an app that registered to receive text files. Consequently, we will display its
content to the user with a pop-up message by using the FileIO class we learned in Chapter 5:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter != null)
{
StorageFile file = e.Parameter as StorageFile;
string content = await FileIO.ReadTextAsync(file);
MessageDialog dialog = new MessageDialog(content);
await dialog.ShowAsync();
}
}
The previous sample tries to open a file called text.log, which is stored in the local storage of
the app. Now the OS will look for any app registered to support the .log extension and it will
pass the file to the default one. However, there is an important difference to highlight between
Windows and Windows Phone.
In fact, Windows allows developers to register any extension (except for the one previously
mentioned such as .bat or .exe), including the ones that are managed by apps included in the
OS itself such as images or audio files. By default, the LaunchFileAsync() method opens the
default app registered for the file type. For example, if you try to open a JPG image, Windows
will open the Photos app. However, you can also allow users to choose which app they want to
use by passing another parameter to the LaunchFileAsync() method, like in the following
sample:
private async void OnOpenFileClicked(object sender, RoutedEventArgs e)
104
{
StorageFile file = await
ApplicationData.Current.LocalFolder.GetFileAsync("text.log");
await Launcher.LaunchFileAsync(file, new LauncherOptions
{
DisplayApplicationPicker = true
});
}
Windows Phone, however, is not able to manage this scenario. We will not be able to manage
our apps file types which are natively supported by the OS such as images, audio or Office
files. We will not get any exception (like when we try to register a prohibited extension such as
.exe); the association will simply not work. Windows Phone will always use the default app to
open such files.
However, Windows Phone also supports the concept of app picker but this applies only to
extensions that are not natively supported by the OS. For example, if we install from the Store
more than one app that is able to manage the .log extension (which is a custom file type not
supported by Windows Phone), the OS will prompt a screen to the user containing a list of all of
the apps that support it.
105
What happens if we do not have any app that is able to manage the required file extension? On
both Windows and Windows Phone, a pop-up message will inform the user and will allow him or
her to search for an app on the Store. The results will automatically be filtered only for the apps
that are able to manage such an extension. However, in Windows, you are also able to force the
file opening. You will be able to choose one of the installed apps even if they did not explicitly
register to support that file extension.
Protocol Activation
The Protocol activation extension is similar to the File activation one except that, instead of
being able to support a file type, we are able to support a URI scheme. This way, we can
intercept when an app tries to invoke a command by using a specific protocol (for example,
log://). The difference is, in this case, the other apps will be able to pass only plain data since
the URI is a string. This extension is often used when we want to provide other apps a way to
run a specific command or to display a specific view of our app. As for the protocol activation,
lets take a look at how to implement this feature both in a source app (the one that will invoke a
URI) and in a target app (the one that will receive the URI).
Receiving a URI
As with the other contracts and extensions, the first step is to add a declaration in the manifest
file. In this case, we need to select, from the Available declarations drop-down menu, the
Protocol item. The only information required is the Name field, which is the name of the URI
scheme we want to register (for example, if we want to subscribe to the log:// schema, we need
to specify log as name). Optionally, you can also specify an image in the Logo field, which will
be used in case there are multiple apps registered to handle the same protocol.
Note: In this scenario, there are also some protocols that are reserved by the system
and cant be registered by a third-party app. You can find the complete list in the MSDN
documentation here.
The next step, as we did for the file activation extension, is to provide a page to redirect the user
to when the app is launched due to a protocol activation. As with every other event connected to
the apps life cycle, we do it in the App class. However, there is not a specific method to manage
the protocol activation so we need to use the generic OnActivated() method and detect if we
are in this scenario by checking the value of the Kind property, like in the following sample:
protected override void OnActivated(IActivatedEventArgs args)
{
if (args.Kind == ActivationKind.Protocol)
{
ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs;
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
106
The approach should be familiar. We simply redirect the user by using the Navigate() method
of the Frame class to the page we added to our project to manage the protocol activation
scenario (in this case, it is called UriViewPage). As navigation parameter, we add the full URI
that has been invoked, which is stored in the Uri property of the activation parameters.
Consequently, we can retrieve this information in the landing page by using the
OnNavigatedTo() method, as usual, like in the following sample that simply shows to the user
a pop-up message with the received URI:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter != null)
{
Uri uri = e.Parameter as Uri;
MessageDialog dialog = new MessageDialog(uri.AbsoluteUri);
await dialog.ShowAsync();
}
}
Opening a URI
A third-party app can invoke a URI simply by calling the LaunchUriAsync() of the same
Launcher class we have seen for the file activation, like in the following sample:
private async void OnLaunchUriClicked(object sender, RoutedEventArgs e)
{
Uri uri = new Uri("log:/text", UriKind.Absolute);
await Launcher.LaunchUriAsync(uri);
}
Also, in this case, when it comes to dealing with the two platforms, we can apply all of the
warnings we have discussed about file activation. Windows allows developers to register any
protocol even if they are supported by native apps. The LaunchUriAsync() directly launches
the default app but you can add a second parameter (which type is LauncherOptions) to
display a picker with the list of all of the apps that have registered to support that protocol, like in
the following sample:
107
For example, the previous code will show the list of all of the browsers installed on your device
that are able to handle the HTTP protocol. However, the previous code will not trigger any
picker in Windows Phone. As developers, you cant override protocols that are registered by
native apps (such as HTTP, which can be opened only by Internet Explorer). You will not get
any exception but your protocol registration will simply be ignored.
The previous code will open the Messaging app in Windows Phone with a new message
already populated with the information that we have defined.
108
As you can see, the EmailMessage class offers some basic properties to customize the email
messages content such as Subject or Body. In addition, it offers three collections to add to the
list of recipients: To (for regular recipients), Cc (for carbon copy recipients), and Bcc (for blind
carbon copy recipients). Every recipient is identified by the EmailRecipient class, which
requires the email address and the recipients name when it is instantiated.
One important feature that has been added with these new APIs (which was not available in
Windows Phone 8.0) is support for attachments. Now you will be able to add a file to the email
message as an attachment by using the Attachment collection:
private async void OnSendMailClicked(object sender, RoutedEventArgs e)
{
EmailMessage mail = new EmailMessage();
mail.Subject = "Subject";
mail.Body = Body";
mail.To.Add(new EmailRecipient("info@qmatteoq.com", "Matteo Pagani"));
StorageFile file = await
ApplicationData.Current.LocalFolder.GetFileAsync("Document.docx");
var stream = RandomAccessStreamReference.CreateFromFile(file);
EmailAttachment attachment = new EmailAttachment(file.Name, stream);
mail.Attachments.Add(attachment);
await EmailManager.ShowComposeNewEmailAsync(mail);
}
109
The first step is to retrieve a reference to the StorageFile object that identifies the file we want
to include in the email message. In the previous sample, we attach a file called Document.docx
stored in the local storage. Then, we create a new EmailAttachment object, which requires as
parameters the file name and the stream with the files content. We retrieve the stream by using
the CreateFromFile() method of the RandomAccessStreamReference class. As parameter,
we pass the StorageFile we have just retrieved. In the end, we just add the EmailAttachment
object we created to the Attachments collection of the EmailMessage class.
Text-to-Speech (TTS): By using a synthesized voice, the app is able to read text to the
user; actually, it is the only scenario that is supported both both Windows and Windows
Phone
Voice commands: Thanks to this feature, the user will be able to open the app and
perform an operation simply by pronouncing a command; this feature is strictly
connected to Cortana. (In countries where the digital assistant is available, Cortana will
take care of managing the command and performing the required operation.)
Speech recognition: Voice commands are useful but they can be triggered only by the
OS when the app is not running; however, when the user enters in our app, we can
continue to use the speech features thanks to a set of APIs that can enable speech
recognition so the user will be able to keep pronouncing commands or dictating text)
110
As you can see, some sentences are embedded into some special tags, such as:
break: It can be used to add a pause before the next sentence is pronounced; by using
the time attribute, you are able to specify the pause length
prosody: It can be used to change how the text should be pronounced; in the previous
sample, we use the rate attribute to set the reading speed
voice: It can be used to specify the kind of voice to use to read the text; in the sample,
we use the gender attribute to simulate a womans voice
The Windows Runtime does not directly support loading a SSML file into the code. You will
have to embed the XML into a string in your app and pass it as parameter of the
SynthesizeSsmlToStreamAsync() method of the SpeechSynthesizer class, like in the
following sample:
111
112
</CommandSet>
</VoiceCommands>
The VCD file includes a base node called CommandSet, which contains all of the commands that
are supported by the app. A CommandSet is identified by two important attributes:
xml:lang: It is the language to which the command set refers; a VCD file can have
multiple CommandSet tags, one for each language supported by the app
Name: It is a unique identifier of the command set
There are two other important information about the CommandSet, which are defined with two
tags:
113
Figure 15: The list of installed apps that are able to support voice commands
Once we have defined the set, we can add all of the commands we want to support by using the
Command tag. Every command is identified by a unique name, which is set with the Name
attribute. In the previous VCD sample, the file contains just one command, identified by the
AddNote name. For each command, we need to specify the following information:
Example: Also in this scenario, we can show the user a sample of the command
ListenFor: It is the most important information since it contains the command that the
user can pronounce. (As you can see from the sample, we can add multiple ListenFor
tags, one for each sentence that we want to support for the same command. A special
feature of the ListenFor tag is that you can wrap some words inside square brackets.
These words are optional; the command will be recognized whether or not the user will
pronounce them. In the previous sample, the command will be activated whether or not
the user has pronounced add a new note or add new note since the word a has
been included inside square brackets)
Feedback: It is a text that will be displayed to the user when the command has been
recognized successfully
Navigate: It is a parameter that is used to define which page of the app will handle the
voice command. (However, in Windows Phone 8.1, it is no longer used since the app will
always be opened by using a specific activation event, which will take care of redirecting
the user to the proper page)
Once we have defined the VCD file, we need to install it into the system by using the
VoiceCommandManager class included in the Windows.Media.SpeechRecognition
namespace, like in the following sample:
private async void OnRegisterCommandClicked(object sender, RoutedEventArgs e)
{
StorageFile file = await
Package.Current.InstalledLocation.GetFileAsync("VoiceCommands.xml");
await VoiceCommandManager.InstallCommandSetsFromStorageFileAsync(file);
}
The first step is to retrieve a reference to the VCD file included in the project. We use the
Package.Current.InstalledLocation class we learned to use in Chapter 5. Then we pass
the file as parameter of the InstallCommandSetsFromStorageFileAsync() method exposed
by the VoiceCommandManager class.
Now the command set is installed and ready to be used. Voice command activation is managed
by the OnActivated() method defined in the App class. Since it is the generic activation
method, we need to check the Kind property to verify that we are in the voice command
scenario. The value we expect is VoiceCommand:
protected override void OnActivated(IActivatedEventArgs args)
{
114
if (args.Kind == ActivationKind.VoiceCommand)
{
VoiceCommandActivatedEventArgs commandArgs = args as
VoiceCommandActivatedEventArgs;
SpeechRecognitionResult result = commandArgs.Result;
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
rootFrame = new Frame();
Window.Current.Content = rootFrame;
Window.Current.Activate();
}
string commandName = result.RulePath.FirstOrDefault();
switch (commandName)
{
case "AddNote":
rootFrame.Navigate(typeof(AddNote));
break;
}
}
}
Thanks to the methods argument, we are able to retrieve the result of the speech recognition
operation, which is stored in the Result property, which is a SpeechRecognitionResult
object. The property we are interested in is called RulePath, which contains all of the voice
commands that have been used to activate the app. Since an app can only be opened by one
command at a time, we retrieve just the first one. The value we will get in return is a string with
the command name we have defined in the VCD file. In the previous sample, since our VCD file
contains only one command definition, we check if the AddNote command has been triggered. If
we fall under this case, we redirect the user to the page that is able to manage the given
command (in this sample, it is a page called AddNote used to add a new note to the list).
115
The OpenNote command contains a keyword embedded into curly braces called number. It is a
parameter which can assume a dynamic value from a set of keywords stored in a PhraseList
element.
As you can see, right after the Command tag, we added a PhraseList section with a set of Item
elements. Each of them is one of the parameters that is accepted by the command and that can
be pronounced by the user. This means that the user will be able to pronounce a command
such as Open the note 2. However, the previous sample has a downside: the list of supported
parameters is fixed while, in most scenarios, the list can be dynamic (since new content can be
included in the app). In our notes management app sample, the user is able to add new notes
and he or she should be able to open them using a voice command.
For this reason, the Windows Runtime includes a method that is able to dynamically update a
PhraseList section, like in the following sample:
private async void OnUpdateListClicked(object sender, RoutedEventArgs e)
{
VoiceCommandSet set =
VoiceCommandManager.InstalledCommandSets["NotesCommandSet"];
await set.SetPhraseListAsync("number", new string[] { "1", "2", "3", "4", "5"
});
}
116
The first step is to retrieve a reference to the CommandSet defined in the VCD file, thanks to the
InstalledCommandSets collection offered by the VoiceCommandManager class. We retrieve it
by using its unique identifier, which has been set in the Name attribute of the CommandSet tag in
the VCD file. Then we can call the SetPhraseListAsync() method, passing two parameters.
The first one is the name of the PhraseList section we want to update (in our case, it is
number, which is the value of the Label property) and the second one is a collection of all of the
parameters we want to support. It is important to mention that this method does not add the new
parameters to the already existing ones but it always overrides the existing list. Consequently,
you will always have to pass all of the supported parameters each time. For example, in a real
notes management app, you will have to add the identifier of each note stored in the app every
time the user adds a new note.
Now, lets take a look at how to change the OnActivated() method of the App class to manage
the voice commands parameter:
protected override void OnActivated(IActivatedEventArgs args)
{
if (args.Kind == ActivationKind.VoiceCommand)
{
VoiceCommandActivatedEventArgs commandArgs = args as
VoiceCommandActivatedEventArgs;
SpeechRecognitionResult result = commandArgs.Result;
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
rootFrame = new Frame();
Window.Current.Content = rootFrame;
Window.Current.Activate();
}
string commandName = result.RulePath.FirstOrDefault();
switch (commandName)
{
case "OpenNote":
if (result.SemanticInterpretation.Properties.ContainsKey("number"))
{
string selectedNote = result.SemanticInterpretation.
Properties["number"].FirstOrDefault();
rootFrame.Navigate(typeof (DetailPage), selectedNote);
}
break;
}
}
}
117
When the app is activated by a voice command that contains a parameter, the
SpeechRecognitionResult object, other than just the pronounced command, contained a
collection called Properties, which is part of the SemanticInterpretation object. This
collection contains a list of all of the supported parameters. We can retrieve the value simply by
using, as key, the name of the parameter, which is the value we assigned to the Label property
of the PhraseList section in the VCD file (in the previous sample, it is number). This way, we
are able to redirect the user to the proper page, adding as the navigation parameter the number
of the note to open, and retrieve it as usual by using the OnNavigatedTo() method of the
destination page:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
string noteId = e.Parameter.ToString();
//load the note with the selected id from the database
}
118
The syntax is the same as we previously saw; the parameter is included into curly braces (in
this case, it is named noteText). The name of the parameter is set with the Label property of
the PhraseTopic tag. That is all. Since the text can be freely dictated, we do not have a fixed
list of supported parameters.
Now, lets take a look at how we can retrieve this new type of parameter in the OnActivated()
method of the App class:
protected override void OnActivated(IActivatedEventArgs args)
{
if (args.Kind == ActivationKind.VoiceCommand)
{
VoiceCommandActivatedEventArgs commandArgs = args as
VoiceCommandActivatedEventArgs;
SpeechRecognitionResult result = commandArgs.Result;
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
rootFrame = new Frame();
Window.Current.Content = rootFrame;
Window.Current.Activate();
}
string commandName = result.RulePath.FirstOrDefault();
switch (commandName)
{
case "AddNote":
if
(result.SemanticInterpretation.Properties.ContainsKey("noteText"))
{
string noteText =
result.SemanticInterpretation.Properties["noteText"].
FirstOrDefault();
rootFrame.Navigate(typeof (DetailPage), noteText);
}
break;
}
}
}
As you can see, the code is the same as we have seen for the standard parameters. If the full
text has been successfully recognized, we will find an item in the Properties collection with, as
key, the identifier we assigned to the PhraseTopic tag in the VCD file. This way, we can
retrieve the text and pass it, as navigation parameter, to a specific page of the app so that we
can retrieve it by using the OnNavigatedTo() method, like in the following sample:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
string noteText = e.Parameter.ToString();
119
Speech Recognition
The last supported scenario for speech services, which again is only available on Windows
Phone, is speech recognition. Once the app is opened, we are no longer able to use the voice
commands feature we have seen before to intercept the users voice. Thanks to the
SpeechRecognizer class, which is part of the Windows.Media.SpeechRecognition
namespace, we are able to implement this feature in our apps.
Before using the speech recognition methods, it is always important to call the
CompileConstraintsAsync() method, which prepares the grammar required to recognize the
pronounced text. Then we can call the RecognizeWithUIAsync() method, which will display
the speech recognition interface. After the recognition is completed, you will get a
SpeechRecognitionResult object in return containing all of the information about the
recognized text.
Thanks to the Status property, we are able to determine whether or not the operation was
successful. If it was, the property is set with the Success value of the
SpeechRecognitionResultStatus enumerator. Consequently, we can retrieve the recognized
text by using the Text property. We also have a way to customize the recognition dialogs
appearance by setting the following properties exposed by the UIOptions object of the
SpeechRecognizer class:
120
ExampleText: It is a text, displayed under the title, which shows an example of the
expected result
IsReadBackEnabled: By default, once the recognition is completed, Windows Phone will
read the recognized text back to the user. (We can disable this behavior by setting this
property to false)
ShowConfirmation: By default, once the recognition is completed, Windows Phone will
show the recognized text in the dialog. (We can disable this behavior by setting this
property to false)
The following sample shows how to customize the recognition dialog by using these properties:
private async void OnRecognizeTextClicked(object sender, RoutedEventArgs e)
{
SpeechRecognizer recognizer = new SpeechRecognizer();
await recognizer.CompileConstraintsAsync();
recognizer.UIOptions.AudiblePrompt = "I'm listening for the note's text";
recognizer.UIOptions.ExampleText = "Remember to buy the milk";
recognizer.UIOptions.IsReadBackEnabled = false;
recognizer.UIOptions.ShowConfirmation = false;
SpeechRecognitionResult result = await recognizer.RecognizeWithUIAsync();
if (result.Status == SpeechRecognitionResultStatus.Success)
{
MessageDialog dialog = new MessageDialog(result.Text);
await dialog.ShowAsync();
}
}
121
122
123
<rule id="rootRule">
<ruleref uri="#openAction" />
<one-of>
<item>the</item>
<item>a</item>
</one-of>
<ruleref uri="#fileWords" />
</rule>
</grammar>
In this file, we can add a set of rules that are identified by the rule tag, which represents the
supported commands (which are included in the one-of section). The order used to define the
commands is important since the user will have to strictly follow it, otherwise the command will
not be recognized. For example, the previous SGRS file can be used to accept commands such
as Open a note or Load a note but The note open will not be accepted since the order does
not match what we have defined.
SGRS files are standard XML files. To use them, you will simply have to add an XML file to your
Visual Studio project. Then, you will be able to load it into your app by using the
SpeechRecognitionGrammarFile class, which requires as parameter when a new instance is
created a StorageFile object that references the SGRS file. The following sample shows how
to load a SGRS file called grammar.xml, which is included in the Visual Studio project:
private async void OnRecognizeTextClicked(object sender, RoutedEventArgs e)
{
SpeechRecognizer recognizer = new SpeechRecognizer();
StorageFile file = await
Package.Current.InstalledLocation.GetFileAsync("grammar.xml");
ISpeechRecognitionConstraint constraint = new
SpeechRecognitionGrammarFileConstraint(file);
recognizer.Constraints.Add(constraint);
await recognizer.CompileConstraintsAsync();
SpeechRecognitionResult result = await recognizer.RecognizeAsync();
if (result.Status == SpeechRecognitionResultStatus.Success &&
result.Confidence != SpeechRecognitionConfidence.Rejected)
{
MessageDialog dialog = new MessageDialog(result.Text);
await dialog.ShowAsync();
}
}
124
Note: If you read Chapter 5, youll know that the previous sample code works only in a
Windows app. Windows Phone requires a different management of the FileOpenPicker
class, since its not possible to keep two applications open at the same time.
125
126
To adapt the user interface to the current status of the player, we can use the CurrentState
property of the MediaElement control, which returns one of the values of the
MediaElementState enumerator. This class contains different values; each of them identifies
one of the possible states, like Playing, Paused, Stopped, etc. We can also get real time
notifications when the status changes, by subscribing to the CurrentStateChanged event. The
following sample code shows how to use the CurrentState property to properly change the
behavior of the user interface. When the Play button is pressed and the player is not playing,
were going to play the media file; otherwise were going to pause it.
private void OnPlayClicked(object sender, RoutedEventArgs e)
{
if (Media.CurrentState == MediaElementState.Paused || Media.CurrentState
== MediaElementState.Stopped)
{
Media.Play();
}
else if (Media.CurrentState == MediaElementState.Playing)
{
Media.Pause();
}
}
The MediaElement control offers many other properties to customize the streaming, like:
127
Consequently, we can use a specific API to enable or disable this behavior. Typically, were
going to disable it when the media file is playing, and enable it again when the media file is
paused or stopped. We can achieve this goal by using the DisplayRequest class, which is
included in the Windows.System.Display namespace. The following sample shows how to
improve the Play method weve already seen, by enabling or disabling the automatic screen
lock feature according to the status of the player:
private void OnPlayClicked(object sender, RoutedEventArgs e)
{
DisplayRequest request = new DisplayRequest();
if (Media.CurrentState == MediaElementState.Paused || Media.CurrentState
==
MediaElementState.Stopped)
{
request.RequestActive();
Media.Play();
}
else if (Media.CurrentState == MediaElementState.Playing)
{
request.RequestRelease();
Media.Pause();
}
}
The automatic screen lock feature is disabled by calling the RequestActive() method of the
DisplayRequest class; when the video is paused, we enable it again by calling the
RequestRelease() method.
128
129
Now we need to apply some changes in the way we manage the MediaElement control. The
first step is to get a reference to the SystemMediaTransportControls class, which is part of
the Windows.Media namespace. Its purpose is to manage all the interactions with the native
player, which can also occur when the application is not running; this way, well be able to
change the playback state accordingly. Consequently, when the page is loaded, we need to get
a reference to this class using the GetForCurrentView() method, and then define which states
and events we want to manage, like in the following sample.
public sealed partial class MainPage : Page
{
private SystemMediaTransportControls systemControls;
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
systemControls = SystemMediaTransportControls.GetForCurrentView();
systemControls.ButtonPressed += SystemControls_ButtonPressed;
systemControls.IsPlayEnabled = true;
systemControls.IsPauseEnabled = true;
Media.CurrentStateChanged += Media_CurrentStateChanged;
}
}
In the OnNavigatedTo() method of the page, we perform a series of important operations:
130
131
}
Every time the user interacts with the background player, the ButtonPressed event is triggered,
and by using the Button property of the methods parameter, we are able to detect which button
has been pressed. Consequently, we need to change the playback status according to the
invoked action. In the previous sample, you can see how to manage the Play and Pause
commands. To perform the commands, we need to use the Dispatcher; this is required
because the background player invokes the ButtonPressed event in a background thread,
while the MediaElement control is declared in the user interface.
132
133
Were going to see very soon which code we will need to include in the agent to manage the
audio playback. But first, lets see how to register the task in the main application. The approach
is similar to the one weve seen for Windows, except that in this case the Entry Point wont be
the app itself, but the background tasks class weve just created. Consequently, well need to
add a Background Tasks item in the Declarations section of the manifest file. After checking
the Audio type, we need to include, in the Entry Point field, the fully qualified name of the
background tasks class, which is the full namespace plus the name of the class. For example, if
we refer to the previous sample, the entry will be AudioPlayerApp.MusicTask.AudioTask.
The next step is to add a reference to the task in the main application, by choosing the Add
reference option when you right-click on the Windows Phone project in the solution. Now the
main application and the task are connected, and we can start writing the required code to make
the background audio work.
In Windows Phone, the main application acts only as an interface to control the playback; all the
dirty work will be done by the background task, which will take care of managing the stream.
The background audio player is identified by the BackgroundMediaPlayer class, which has a
unique instance across the whole system: we cant have two apps at the same time that control
the background audio playback. When youre going to get a reference to the current instance of
the class, the background task will be automatically executed, and the Run() method invoked.
Here is a sample initialization of the main application:
public sealed partial class MainPage : Page
{
private MediaPlayer mediaPlayer;
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
mediaPlayer = BackgroundMediaPlayer.Current;
}
}
134
Messages are important because the playback management is controlled by the background
task; every time the user interacts with the interface (for example, by playing the Play button),
we wont control the player directly, but instead send a message to the background task, which
will execute the required action. Messages are identified by the ValueSet class, which is simply
a collection of objects identified by a unique key. Inside this collection, we can add multiple
values, which can be retrieved by the receiver of the message.
Messages are exchanged using two methods exposed by the BackgroundMediaPlayer class:
On the other side, the BackgroundMediaPlayer class offers two specific events to intercept
messages, which are:
To better understand how to use messages, lets see a real example. Lets assume that, in the
main application, we have a button that can be used to start the playback. Here is the code that
is executed:
private void OnPlayClicked(object sender, RoutedEventArgs e)
{
ValueSet message = new ValueSet();
message.Add("title", "A Sky Full Of Stars");
BackgroundMediaPlayer.SendMessageToBackground(message);
}
Weve created a new message, which is the ValueSet object. Then, in the same way we would
have done with a regular Dictionary collection, we add a new item to the message. In this
case, its the title of the audio track to reproduce, identified by a unique key, which is title. The
message is sent to the background task by calling the SendMessageToBackground() method of
the BackgroundMediaPlayer class.
What happens when the background task has started to play a new track and we want to
display the information in the application? Here is a sample code:
public sealed partial class MainPage : Page
{
135
136
137
Audio background tasks work in a different way than standard background tasks. As we will see
in Chapter 11, a regular background task typically executes the Run() method, and is then
disposed by the operating system. Consequently, the BackgroundTaskDeferral object is
created when the Run() method starts, and is marked as completed when the method is
completed. However, background audio tasks are always kept alive until theres a media file to
play. Consequently, we call the GetDeferral() method in the Run() block, but we invoke the
Complete() method only when the background task is actually disposed, which happens when
the Completed or Canceled events are triggered.
Its time to go back to the real background task implementation. Lets see, in detail, how to
manage the ButtonPressed event of the SystemMediaTransportControls.
void transportControls_ButtonPressed(SystemMediaTransportControls sender,
SystemMediaTransportControlsButtonPressedEventArgs args)
{
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
BackgroundMediaPlayer.Current.Play();
break;
case SystemMediaTransportControlsButton.Pause:
BackgroundMediaPlayer.Current.Pause();
break;
}
}
The code is similar to the one weve seen for Windows; by using the Button property of the
methods parameter, we are able to understand which button has been pressed. According to
this information, we trigger the appropriate method of the BackgroundMediaPlayer class. The
previous sample shows how to manage the Play and Pause buttons.
Another important event weve subscribed in the Run() method is
MessageReceivedFromForeground, which you should already know. Its triggered when the
main application sends a message to the background task because the user wants to change
the playback status.
void BackgroundMediaPlayer_MessageReceivedFromForeground(object sender,
MediaPlayerDataReceivedEventArgs args)
{
ValueSet valueSet = args.Data;
foreach (string key in valueSet.Keys)
{
switch (key)
{
case "Play":
Play(valueSet[key].ToString());
break;
138
case "Pause":
BackgroundMediaPlayer.Current.Pause();
break;
case "Resume":
BackgroundMediaPlayer.Current.Play();
break;
}
}
}
Thanks to the Data property, we are able to retrieve the message, which type is ValueSet.
Then we can iterate the Keys collection to understand which command has been received.
According to the content of the message, we perform a different operation with the
BackgroundMediaPlayer class. What happens when we want to play a track? In this case, in
addition to the key, we also need to retrieve the content of the message, which is the URL of the
file to reproduce. The following sample shows how to define the Play() method:
private void Play(string url)
{
BackgroundMediaPlayer.Current.SetUriSource(new Uri(url));
BackgroundMediaPlayer.Current.Play();
transportControls.DisplayUpdater.Type = MediaPlaybackType.Music;
transportControls.DisplayUpdater.MusicProperties.Title = "WPDev Fusion
1";
transportControls.DisplayUpdater.Update();
ValueSet info = new ValueSet();
info.Add("title", "WPDev Fusion 1");
BackgroundMediaPlayer.SendMessageToForeground(info);
}
The track is set by using the SetUriSource() method of the BackgroundMediaPlayer class,
passing, as parameter, the URL (which is the content of the message weve received). Then, we
can simply call the Play() method. In the previous sample, you can also see that we are able to
set what information to display in the background audio player, like we did in Windows, by using
the DisplayUpdater property of the SystemMediaTransportControls class. This time, in
addition to updating the background player, we also send the information about the title of the
current track to the main application, by sending a new message with the
SendMessageToForeground() method. This way, if the application is running in the foreground,
it will be able to intercept it and display the title of the new track to the user.
The last piece of code to analyze is the CurrentStateChanged event handler, which is invoked
every time the playback state changes. Were going to use it in the same way we did for
Windows: to synchronize the playback state with the background audio player state, so that they
always match.
private void Current_CurrentStateChanged(MediaPlayer sender, object args)
{
139
if (sender.CurrentState == MediaPlayerState.Playing)
{
transportControls.PlaybackStatus = MediaPlaybackStatus.Playing;
}
else if (sender.CurrentState == MediaPlayerState.Paused)
{
transportControls.PlaybackStatus = MediaPlaybackStatus.Paused;
}
}
We simply the detect the current state, using the CurrentState property of the
BackgroundMediaPlayer class, and we set the PlaybackStatus property of the
SystemMediaTransportControls accordingly.
Internet connection: The user is immediately able to share their photo with friends on
social networks, etc.
Applications: The user can edit the photo immediately, by adding effects, fixing
imperfections, etc.
Camera evolution: In the beginning, the integrated camera was very poor compared to
standard photo cameras, but things have dramatically changed in the latest years.
Phones like the Lumia 1020 can completely replace a compact camera.
The Windows Runtime offers a set of APIs that, as developers, we can use to integrate the
camera experience into our application. Lets see them in detail.
140
To use this feature, we need to use a class called CameraCaptureUI, which belongs to the
Windows.Media.Capture namespace. To properly use it, we need to enable the Webcam
capability in the manifest file. Additionally, if we want to record a video with audio, we need to
enable the Microphone capability.
Capturing a photo
Before capturing a photo, we need to configure some settings to customize the operation, which
can be accessed using the PhotoSettings property of the CameraCaptureUI class. The most
important ones are:
Format, which is the file type we want to use to save the photo. It can be Jpg or Png,
and its set using one of the values of the CameraCaptureUIPhotoFormat enumerator.
MaxResolution, which is the resolution to use to acquire the photo. Its set using the
CameraCaptureUIMaxPhotoResolution enumerator, which accepts a range of values
from VerySmallQvga (very low quality) to HighestAvailable (the best supported
quality).
Once youve defined the capture settings, you can start the capturing process by calling the
CaptureFileAsync() method. It requires as parameter the kind of media we want to capture
(since were talking about photos, we need to pass CameraCaptureUIMode.Photo as value).
This method will open the Camera application so that the user can take the picture. The
captured photo will be passed back to your application as a StorageFile object. The following
sample shows how to use these APIs to capture a photo and display it to the user with an image
control:
private async void OnTakePictureClicked(object sender, RoutedEventArgs e)
{
CameraCaptureUI capture = new CameraCaptureUI();
capture.PhotoSettings.Format = CameraCaptureUIPhotoFormat.Jpeg;
capture.PhotoSettings.MaxResolution = CameraCaptureUIMaxPhotoResolution.
HighestAvailable;
StorageFile file = await
capture.CaptureFileAsync(CameraCaptureUIMode.Photo);
if (file != null)
{
var stream = await file.OpenReadAsync();
BitmapImage image = new BitmapImage();
await image.SetSourceAsync(stream);
CapturedImage.Source = image;
}
}
141
Capturing a video
The code needed to capture a video is very similar to the one weve just seen. The main
difference is that, if we want to customize the capture settings, we need to interact with the
VideoSettings property offered by the CameraCaptureUI class. Also in this scenario, we can
customize the recording format (which can be MP4 or WMV, and is set by using one of the
values of the CameraCaptureUIVideoFormat enumerator) and the resolution.
Again, to launch the Camera application, we need to call the CaptureFileAsync() method,
passing as parameter, this time, the value CameraCaptureUIMode.Video. What we get in
return is the captured video as a StorageFile object. The following sample shows how to
record a video and to display it to the user using a MediaElement control:
private async void OnRecordVideoClicked(object sender, RoutedEventArgs e)
{
CameraCaptureUI capture = new CameraCaptureUI();
capture.VideoSettings.Format = CameraCaptureUIVideoFormat.Mp4;
capture.VideoSettings.MaxResolution = CameraCaptureUIMaxVideoResolution.
HighestAvailable;
StorageFile file = await
capture.CaptureFileAsync(CameraCaptureUIMode.Video);
if (file != null)
{
var stream = await file.OpenReadAsync();
RecordedVideo.SetSource(stream, stream.ContentType);
}
}
142
By combining the CaptureElement control and the MediaCapture class, we are able to display
the camera preview in our application with the following code:
public sealed partial class MainPage : Page
{
private MediaCapture capture;
public MainPage()
{
this.InitializeComponent();
}
private async void OnInitializeCameraClicked(object sender,
RoutedEventArgs e)
{
capture = new MediaCapture();
await capture.InitializeAsync();
PreviewStream.Source = capture;
await capture.StartPreviewAsync();
}
}
After we have created a new MediaCapture object, we initialize it by calling the
InitializeAsync() method. Then, we can assign it as Source of the MediaCapture element
weve included in our page. Now we are able to display the preview by calling the
StartPreviewAsync() method.
However, the previous code is incomplete; it doesnt take into account that the application can
run on a phone with two cameras, one on the back and one on the front. To support this
scenario, the InitializeAsync() method also supports a parameter, which type is
MediaCaptureInitializationSettings. Among the settings we can customize, we can
define which camera to use. Here is a more complete initialization of the preview stream:
private async void OnInitializeCameraClicked(object sender, RoutedEventArgs
e)
{
MediaCapture capture = new MediaCapture();
DeviceInformationCollection collection = await DeviceInformation.
FindAllAsync(DeviceClass.VideoCapture);
MediaCaptureInitializationSettings settings = new
MediaCaptureInitializationSettings();
if (collection.Count > 1)
{
settings.VideoDeviceId = collection[1].Id;
}
else
{
settings.VideoDeviceId = collection[0].Id;
143
}
await capture.InitializeAsync(settings);
PreviewStream.Source = capture;
await capture.StartPreviewAsync();
}
The first step is to query the operating system to detect how many cameras are available. We
do it by calling the FindAllAsync() method of the DeviceInformation class, specifying that
we are interested in the VideoCapture devices (which is one of the values of the DeviceClass
enumerator). This method returns a collection with all the available cameras, each with its
unique id, which is stored in the Id property. This is the identifier that we need to assign to the
VideoDeviceId property of the MediaCaptureInitializationSettings class. The previous
code tries to use the front-facing camera, by checking how many cameras the device has. If
theres more than one, theres a front camera we can use; otherwise, we fall back on the default
camera, which is the rear one. After weve properly defined the VideoDeviceId property, we
can pass the MediaCaptureInitializationSettings object weve created to the
InitializeAsync() method.
The code weve seen so far is the same, whether we want to take a photo or to record a video.
From now on, however, were going to use different methods, according to our scenario.
Capturing a photo
Photos are taken using the CapturePhotoToStorageFileAsync() method offered by the
MediaCapture class. As parameters, it requires the file format we want to use (thanks to the
ImageEncodingProperties class) and the file where we want to save the captured image
(represented with a StorageFile object). The following sample shows how to take a picture
and save it in a file called image.jpg in the local storage:
private async void OnTakePhotoClicked(object sender, RoutedEventArgs e)
{
StorageFile file = await ApplicationData.Current.LocalFolder.
CreateFileAsync("image.jpg", CreationCollisionOption.ReplaceExisting);
ImageEncodingProperties properties =
ImageEncodingProperties.CreateJpeg();
await capture.CapturePhotoToStorageFileAsync(properties, file);
}
The ImageEncodingProperties class offers different methods to create the photo with many
formats. The previous sample uses the CreateJpeg() method to create a JPEG file; however,
we could have used CreateBmp() to create a bitmap file, CreatePng() to create a PNG file, or
CreateUncompressed() to acquire a raw image.
144
Capturing a video
The procedure to capture a video is very similar to the one weve seen for taking pictures. The
difference is that, this time, were going to use the StartRecordToStorageFileAsync()
method of the MediaCapture class. As parameters, other than the usual StorageFile object,
which identifies the file in which to save the content, it requires a MediaEncodingProfile
object that defines the format and the quality to use.
private async void OnRecordVideoClicked(object sender, RoutedEventArgs e)
{
StorageFile file = await
ApplicationData.Current.LocalFolder.CreateFileAsync("video.mp4",
CreationCollisionOption.ReplaceExisting);
MediaEncodingProfile profile =
MediaEncodingProfile.CreateMp4(VideoEncodingQuality.
HD720p);
await capture.StartRecordToStorageFileAsync(profile, file);
}
Also in this case, the MediaEncodingProfile class offers many methods to work with the most
common video types. The previous example uses the CreateMp4() method to create a MP4
video, but we could have used CreateWmv() to create a WMV file, or CreateAVI() to create an
AVI file. All these methods also require a parameter, which type is VideoEncodingQuality, to
define the recording quality; in the previous sample, the resolution of the captured video will be
720p.
Brightness
Contrast
Exposure
Focus
FlashControl, to enable or disable the flash
The following sample shows how to disable the flash, by modifying the FlashControl property
of the VideoDeviceController object:
private async void OnTakePhotoClicked(object sender, RoutedEventArgs e)
{
StorageFile file = await
ApplicationData.Current.LocalFolder.CreateFileAsync("image.jpg",
CreationCollisionOption.ReplaceExisting);
ImageEncodingProperties properties =
145
ImageEncodingProperties.CreateJpeg();
capture.VideoDeviceController.FlashControl.Enabled = false;
await capture.CapturePhotoToStorageFileAsync(properties, file);
}
In case of a video recording, you can also customize the audio recording parameters, by using
the AudioDeviceController. The following sample shows how to change the recording
volume level, by interacting with the VolumePercent property:
private async void OnRecordVideoClicked(object sender, RoutedEventArgs e)
{
StorageFile file = await
ApplicationData.Current.LocalFolder.CreateFileAsync("video.mp4",
CreationCollisionOption.ReplaceExisting);
MediaEncodingProfile profile =
MediaEncodingProfile.CreateMp4(VideoEncodingQuality.
HD720p);
capture.AudioDeviceController.VolumePercent = 30;
await capture.StartRecordToStorageFileAsync(profile, file);
}
To access to these libraries, youll have to enable a set of capabilities in the manifest file; theres
a capability for each supported library.
146
Working with libraries is very easy if youve read Chapter 5; every library is mapped with a
StorageFolder object, so youll be able to read, write, and copy files in the same way you do
with the local storage. The following sample shows how to list all the images that are saved in
the users pictures library, and to display their names to the user:
private async void OnGetImagesClicked(object sender, RoutedEventArgs e)
{
var files = await KnownFolders.PicturesLibrary.GetFilesAsync();
foreach (StorageFile file in files)
{
MessageDialog dialog = new MessageDialog(file.DisplayName);
await dialog.ShowAsync();
}
}
The following sample, instead, shows how to take a picture stored in the local storage (called
image.png) and save it in the pictures library, using the CopyAsync() method:
private async void OnSaveImageClicked(object sender, RoutedEventArgs e)
{
var file = await
ApplicationData.Current.LocalFolder.GetFileAsync("image.png");
await file.CopyAsync(KnownFolders.PicturesLibrary);
}
Theres an important concept to understand when working with libraries: a library doesnt refer
to a specific file type, but to a specific device location. The difference is clear when it comes to
the camera roll. As default behavior, every photo or video acquired with the device is stored into
the Camera Roll folder. Consequently, if you try to retrieve all the video files using the
KnownFolders.VideosLibrary class, you wont get all the videos acquired with the device,
because they arent stored in the video folder, but in the camera roll folder. This means that, for
example, if you want to retrieve only the acquired video, youll have to interact with the class
KnownFolders.CameraRoll and use the FileType property of the StorageFile class to
identify the file type, like in the following sample:
147
148
The Windows Runtime offers a way to retrieve this special properties, by offering a set of
methods (which are exposed by the Properties class of the StorageFile object) for each
media type:
Every method returns a different kind of object, with different properties. With music, for
example, youll get in return a MusicProperties object, which offers properties like Artist or
Title. For pictures, youll get in return an ImageProperties object, with properties like Width
or Height. The following sample code shows how to retrieve the EXIF data of the first picture in
the camera roll, so that we can display the title and resolution to the user with a pop-up
message:
private async void OnGetFilePropertiesClicked(object sender, RoutedEventArgs
e)
{
StorageFolder library = KnownFolders.CameraRoll;
IReadOnlyList<StorageFile> pictures = await library.GetFilesAsync();
StorageFile picture = pictures.FirstOrDefault();
if (picture != null)
{
ImageProperties imageProperties = await picture.Properties.
GetImagePropertiesAsync();
if (imageProperties != null)
{
string info = string.Format("{0} - {1} x {2}",
imageProperties.Title, imageProperties.Width, imageProperties.Height);
MessageDialog dialog = new MessageDialog(info);
await dialog.ShowAsync();
}
}
}
149
150
Toast notifications are the most visible, since they display a banner at the top of the
screen. They can also play a sound and trigger a device vibration.
Tile notifications are used to update the content of a tile. They are non-intrusive, since
they dont trigger any alert; the tile is silently updated with the new information.
Badge notifications are used in combination with tiles, and instead of changing the
whole tile content, they simply display a symbol or a number to capture the users
attention.
Raw notifications dont have a fixed structure, and can contain any type of content.
Regardless of the type were going to use, all the notifications (except the raw ones) are
represented using the XML format, as well see later in this chapter. Well also talk about push
notifications, which make it much easier for a server application (like a website or a web service)
to send a notification. In addition, the Windows Runtime is able to automatically adapt the
notifications content to the platform guidelines. For example, to send a toast notification, well
use the same exact content, even if it will be displayed in a different way in Windows and in
Windows Phone.
If we exclude raw notifications (which can be sent only by a remote server), we can send
notifications in four different ways:
151
From the application itself: If the application content changes while the user is using it,
we can immediately update the tile from code.
With a background task: Well see how they work in detail in Chapter 11.
Periodic: The application periodically pulls an XML file published on Internet with the
notifications content, and updates it.
Scheduled: The application is able to schedule a set of notifications, which are sent at
the exact date and time weve specified.
Push notifications: The notification is generated by an external service (like a web
application) and sent to the device using a service provided by Microsoft called WNS
(Windows Notification Service).
Toast notifications
As weve already mentioned, toast notifications are the easiest for the user to see, so you
should avoid abusing them. The following image shows how toast notifications are rendered in a
different way on both platforms:
Figure 17: The different ways toast notifications are displayed on Windows and Windows Phone
The first step to receive toast notifications is to enable them in the manifest file. In the
Application section, under the Notifications category, well find a dropdown menu labeled
Toast capable. We need to set it to Yes before starting to work with the code.
There are eight templates that can be used to send a toast notifications, and they are all
documented in the MSDN documentation at http://s.qmatteoq.com/ToastCatalog. The various
templates support different content types: text, images, etc. However, most of these templates
work only with Windows. On Windows Phone, we can use only one template, which is
composed of the applications logo, a title, and a text (as seen in the previous figure, on the
right). You can find this template in the documentation with the name ToastText02. However,
we can use any template we want on Windows Phone; the information that isnt supported by
the platform (like the image) will simply be ignored.
The following sample shows the XML structure of a notification, which template is
ToastImageAndText02:
<toast>
<visual>
<binding template="ToastImageAndText02">
<image id="1" src="image1" alt="image1"/>
152
<text id="1">headlineText</text>
<text id="2">bodyText</text>
</binding>
</visual>
</toast>
The main node is called binding, and it has a template attribute that describes the name of
the used template. Inside the binding node, we find an element for each property supported by
the notification. In this case, we have one image element (for the image) and two text elements
(for the text that is displayed next to the image). The following sample shows how to generate,
in code, a toast notification using the previous template:
private void OnSendToastClicked(object sender, RoutedEventArgs e)
{
XmlDocument template =
ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastImageAndTe
xt02);
XmlNodeList texts = template.GetElementsByTagName("text");
texts[0].AppendChild(template.CreateTextNode("Title"));
texts[1].AppendChild(template.CreateTextNode("Text"));
XmlNodeList images = template.GetElementsByTagName("image");
((XmlElement)images[0]).SetAttribute("src", "msappx:///Assets/wplogo.png");
ToastNotification notification = new ToastNotification(template);
ToastNotifier notifier = ToastNotificationManager.CreateToastNotifier();
notifier.Show(notification);
}
As already mentioned at the beginning of the chapter, every notification is represented using the
XML format. Consequently, youll need to define them in code by manually manipulating the
XML structure. However, you wont have to manually create the whole XML that defines the
notification. The Windows Runtime offers a class called ToastNotificationManager, which,
using the GetTemplateContent() method, can retrieve the XML structure of a notification. The
method requires, as a parameter, one of the values of the ToastTemplateType enumerator;
each value matches one of the templates that is described in the MSDN documentation.
What we get in return is an XmlDocument object with the XML structure. Using the APIs
provided by the Windows Runtime to interact with XML documents, we are able to:
153
Change the text displayed in the notification, by retrieving a reference to all the text
elements with the GetElementsByTagName() method. Thanks to the AppendChild()
method, we are able to change the elements values and specify the notifications title
and text.
Change the image displayed in the notification, by again using the
GetElementsByTagName() method, and by retrieving a reference to the image element.
As with the HTML image element, we need to change the src property of the element to
define the path of the image to use. In the previous sample, since were using the msappx:/// prefix, were loading an image from the Visual Studio project.
Once weve defined the XML layout, we can create the notification by using the
ToastNotification class, which requires as parameter the XML document weve just
customized. In the end, we need to create a ToastNotifier object, which will take care of
sending the notification. To create it, we need to use the CreateToastNotifier() method of
the ToastNotificationManager class. In the end, we can show the notification simply by
calling the Show() method of the ToastNotifier class and passing, as parameter, the
ToastNotification object weve previously created.
154
{
// When the navigation stack isn't restored navigate to the first
page,
// configuring the new page by passing required information as a
navigation
// parameter
if (!rootFrame.Navigate(typeof(MainPage), e.Arguments))
{
throw new Exception("Failed to create initial page");
}
}
// Ensure the current window is active
Window.Current.Activate();
}
As you can see, compared to the standard OnLaunched() method, weve added a new check: if
the Arguments property of the methods parameter isnt empty, it means that the application has
been launched by a toast notification. In this case, instead of redirecting the user to the main
page, well force the navigation directly to another page (in the previous sample, its called
DetailPage) and we pass, as navigation parameter, the one weve received from the
notification. This way, well be able to directly load and display to the user the content connected
to the toast notification that was displayed.
155
The library offers a class called ToastContentFactory, which is able to create the notification
template: we can find many methods, one for each supported template. In this case, since were
using the template called ToastImageAndText02, we use the CreateToastImageAndText02()
method. In return, we get an object that offers a way to customize the notification content simply
by changing the values of its properties. In the previous sample, we set:
In the end, we transform the template into a ToastNotification object, by calling the
CreateNotification() method. From this point, the code is the same as weve seen before:
we create a new ToastNotifier object using the ToastNotificationManager class, and we
pass the notification to the Show() method.
156
The previous sample uses the NotificationsExtensions library weve previously seen. The
difference is that we create a new ScheduledToastNotification object, which requires as
parameter the XML that defines the notification (instead of a ToastNotification object). For
this purpose, were going to use the GetXml() method offered by the template, which will return
the XML file. The other parameter required by the ScheduledToastNotification constructor
is the date and time to show the notification, represented by a DateTimeOffset object. In the
previous sample, we schedule it 10 seconds later than the current time. In the end, we add the
notification to the scheduler by calling the AddToSchedule() method of the ToastNotifier
class.
The ToastNotifier class offers a way to retrieve the list of scheduled notifications, by using
the GetScheduledToastNotifications() method. We can also remove already scheduled
notifications by using the RemoveFromSchedule() method. The following sample shows how to
remove all the notifications that have been scheduled by the app:
private void OnRemoveNotifications(object sender, RoutedEventArgs e)
{
ToastNotifier notifier = ToastNotificationManager.CreateToastNotifier();
IReadOnlyList<ScheduledToastNotification> notifications =
notifier.GetScheduledToastNotifications();
foreach (ScheduledToastNotification notification in notifications)
{
notifier.RemoveFromSchedule(notification);
}
}
157
158
Lets analyze the following sample, which generates two toast notifications:
private void OnSendToastClicked(object sender, RoutedEventArgs e)
{
IToastImageAndText02 template =
ToastContentFactory.CreateToastImageAndText02();
template.TextHeading.Text = "Title";
template.TextBodyWrap.Text = "Text";
template.Image.Src = "ms-appx:///Assets/wplogo.png";
ToastNotification notification = template.CreateNotification();
notification.Tag = "Notification";
notification.Group = "MyNotifications";
ToastNotifier notifier = ToastNotificationManager.CreateToastNotifier();
notifier.Show(notification);
159
IToastImageAndText02 template2 =
ToastContentFactory.CreateToastImageAndText02();
template2.TextHeading.Text = "Title 2";
template2.TextBodyWrap.Text = "Text 2";
template2.Image.Src = "ms-appx:///Assets/wplogo.png";
ToastNotification notification2 = template.CreateNotification();
notification.Tag = "Notification 2";
notification.Group = "MyNotifications";
notifier.Show(notification2);
}
As you can see, weve assigned a different Tag to the two notifications, but the same Group
name. Thanks to these properties, well be able to interact with these notifications using the
ToastNotificationHistory class. For example, we can use the Remove() method to remove
a single notification, passing its tag as parameter:
private void OnRemoveNotificationClicked(object sender, RoutedEventArgs e)
{
ToastNotificationHistory history = ToastNotificationManager.History;
history.Remove("Notification");
}
Another option is to delete all the notifications that belong to the same group. The following
sample shows how to remove all the notifications that belong to a group called
MyNotifications, which is passed as parameter to the RemoveGroup() method:
private void OnRemoveGroupClicked(object sender, RoutedEventArgs e)
{
ToastNotificationHistory history = ToastNotificationManager.History;
history.RemoveGroup("MyNotifications");
}
In the end, we can also replace an already existing notification. We create a new
ToastNotification object, change the content we want to modify, and then send it again, by
assigning the same Tag and Group value of an already existing notification.
private void OnSendToastClicked(object sender, RoutedEventArgs e)
{
IToastImageAndText02 template =
ToastContentFactory.CreateToastImageAndText02();
template.TextHeading.Text = "Updated title";
template.TextBodyWrap.Text = "Update text";
template.Image.Src = "ms-appx:///Assets/wplogo.png";
ToastNotification notification = template.CreateNotification();
notification.Tag = "Notification";
160
notification.Group = "MyNotification";
ToastNotifier notifier = ToastNotificationManager.CreateToastNotifier();
notifier.Show(notification);
}
We can also remove all the notifications that belong to our application, simply by calling the
Clear() method of the ToastNotificationHistory class, like in the following sample:
private void OnRemoveAllNotificationsClicked(object sender, RoutedEventArgs
e)
{
ToastNotificationHistory history = ToastNotificationManager.History;
history.Clear();
}
Tile notifications
Weve already highlighted the importance of providing a great tile experience with our
application; a good tile is able to keep the user continuously updated on the applications
content, without requiring them to open it to perform any task, or to catch their attention and
draw them into the app. The weather application is a very good sample of this scenario: users
are able to immediately discover the weather forecast in their current position simply by glancing
at their start screen. However, if they want to see additional information (like the long-term
forecast), they can still open the application.
Tile notifications work a lot like toast notifications, with a rich catalog of available templates,
represented using XML. However, an important difference with toast notifications is that we
need to provide a default tile. The user, in fact, could decide to pin our application on his Start
Screen at any time, even when no notifications have been sent. The default tiles are set in the
manifest file, in the Visual Assets section. We can define many assets, since Windows and
Windows Phone support multiple tile sizes. The required images are:
161
Square70x70 (in Windows) and Square71x71 (in Windows Phone), the smallest square
tile.
Square150x150, the standard square tile.
Wide310x150, the wide rectangular tile.
Square310x310, a big square tile, available only in Windows.
A background image. Its important to highlight that the image should have a maximum
resolution of 500x500, and should be smaller than 500 KB. Otherwise, the notification
will be discarded.
A text to show on the tile.
The text color (dark or light). This option is available only in Windows.
The background color to apply to the tile. In Windows Phone, you can also use the
transparent value: the tile will automatically adapt to the users theme, and it will be
able to properly support the start background image.
The default size that is used when the user pins the application on the Start screen. This
option is available only in Windows. On Windows Phone, by default, tiles are created
using the Square150x150 format.
Once youve defined the default tile aspect, you can start sending tile notifications to update
them. The template catalog is very rich, as you can see in the MSDN documentation at
http://s.qmatteoq.com/TileTemplatesCatalog. Most of the templates will be automatically
adapted to the platform, so that youll be able to send the same notification payload to Windows
and Windows Phone.
There are three kind of tiles in the template catalog:
Peek tiles: These tiles support an animation to display a secondary content. The kind of
animation is different according to the platform. On Windows, the main content will slide
on the top, and then the secondary one will be displayed; on Windows Phone, the tile
will flip to display the secondary content that is placed on the back side.
162
Block tiles: These tiles are used when the most important information you want to
deliver is a number (like the Calendar app, which shows the current day).
ImageCollection tiles: These are used to display a collection of images, by applying a
mosaic animation similar to the one used by the People application.
Templates are also grouped according to the tile size, so youll find specific templates for the
following sizes:
Windows also offers a small square tile size, which is called Square70x70; however, you wont
find any template in the catalog list for this size, since we can only update these files with a
badge notification (which well see later).
Lets see now how to update a tile. Were going to use, as reference, the template called
TileSquare150x150PeekImageAndText01, which is defined in the following way:
<tile>
<visual version="2">
<binding template="TileSquare150x150PeekImageAndText01">
<image id="1" src="image1" alt="alt text"/>
<text id="1">Text Field 1 (larger text)</text>
<text id="2">Text Field 2</text>
<text id="3">Text Field 3</text>
<text id="4">Text Field 4</text>
</binding>
</visual>
</tile>
The main element is called tile, and it contains another element called visual, which includes
the visual definition of the tile. The most important element is binding, which defines, thanks to
the template property, which template the XML is going to define. Inside the binding element,
were going to find an element for each information of the tile that we can customize; in the
previous sample, the template supports one image and four text elements. However, as weve
seen for toast notifications, we dont have to manually define the XML file. Thanks to the
TileUpdateManager class, we can retrieve the template we need by using the
GetTemplateContent() method and passing as parameter one of the values of the
TileTemplateType enumerator. The following sample shows how to create a
TileSquare150x150PeekImageAndText01 tile, and how to customize its properties:
private void OnUpdateTileClicked(object sender, RoutedEventArgs e)
{
XmlDocument template =
TileUpdateManager.GetTemplateContent(TileTemplateType.
TileSquare150x150PeekImageAndText01);
163
164
Consequently, before updating the tile using the TileUpdater class, we need to define the new
template and add it as a child of the visual tag, like in the following sample:
private void OnUpdateTileClicked(object sender, RoutedEventArgs e)
{
XmlDocument template =
TileUpdateManager.GetTemplateContent(TileTemplateType.
TileSquare150x150PeekImageAndText01);
XmlNodeList texts = template.GetElementsByTagName("text");
texts[0].InnerText = "Tile title";
texts[1].InnerText = "Text 1";
texts[2].InnerText = "Text 2";
texts[3].InnerText = "Text 3";
XmlNodeList images = template.GetElementsByTagName("image");
((XmlElement)images[0]).SetAttribute("src", "msappx:///Assets/wplogo.png");
XmlDocument wideTileXml =
TileUpdateManager.GetTemplateContent(TileTemplateType.
TileWide310x150PeekImageAndText01);
XmlNodeList wideTileTexts = wideTileXml.GetElementsByTagName("text");
wideTileTexts[0].AppendChild(wideTileXml.CreateTextNode("Wide tile
tile"));
IXmlNode node =
template.ImportNode(wideTileXml.GetElementsByTagName("binding").Item(0),
true);
template.GetElementsByTagName("visual").Item(0).AppendChild(node);
TileNotification notification = new TileNotification(template);
TileUpdater updater =
TileUpdateManager.CreateTileUpdaterForApplication();
updater.Update(notification);
}
After weve defined the tile using the TileSquare150x150PeekImageAndText01 template as
we did before, we also define a new tile using the TileWide310x150PeekImageAndText01
template. Then, before sending the update, we combine the two templates into a single XML
file, and, only at the end, we create a new TileNotification object.
165
166
updater.Update(wideNotification);
}
We define both the square and wide templates, and we set the Square150x150Content
property of the wide template with the square one weve previously defined. Then we just need
to create a TileNotification object starting from the bigger tile notification, which contains
the definition of the smaller one (in this case, the small square one). This is the object to pass
as parameter of the Update() method of the TileUpdater class.
167
We have create two notifications, but before sending the first one, weve called the
EnableNotificationsQueue() method of the TileUpdater class. The result of this code is
that the second update wont overwrite the first one, and the two notifications will be displayed
on the main tile alternatively.
Thanks to the Tag property, we can also replace a queued notification, as we did to replace
toast notifications in the Action Center in Windows Phone. Its enough, in fact, to send a new
notification with the same Tag property of an existing notification:
private void OnUpdateTileClicked(object sender, RoutedEventArgs e)
{
ITileSquare150x150PeekImageAndText01 template =
TileContentFactory.CreateTileSquare150x150PeekImageAndText01();
template.TextHeading.Text = "Title 1";
template.TextBody1.Text = "Text 1";
template.TextBody2.Text = "Text 2";
template.TextBody3.Text = "Text 3";
template.Image.Src = "ms-appx:///Assets/wplogo.png";
TileNotification notification = template.CreateNotification();
notification.Tag = "MyNotification";
TileUpdater updater =
TileUpdateManager.CreateTileUpdaterForApplication();
updater.EnableNotificationQueue(true);
updater.Update(notification);
}
In the previous sample, we assigned the MyNotification value to the Tag property. From now
on, if we send new tile notifications with a different tag, they will be simply added to the queue.
However, if we send another notification with the same tag, it wont be queued, but it will replace
the existing one.
168
template.Image.Src = "ms-appx:///Assets/wplogo.png";
XmlDocument xml = template.GetXml();
TileUpdater updater =
TileUpdateManager.CreateTileUpdaterForApplication();
ScheduledTileNotification scheduledTile = new
ScheduledTileNotification(xml,
DateTimeOffset.Now.AddSeconds(5));
updater.AddToSchedule(scheduledTile);
}
Were using the NotificationsExtensions library, so we call the GetXml() method on the
template to get the XML definition of the notification. After weve created the
ScheduledTileNotification object, we schedule the update using the AddToSchedule()
method of the TileNotifier class.
169
Now you have two ways to perform periodic updates. The first one is to configure them in the
manifest file. This solution is useful when you want to start receiving tile updates immediately
after the application has been installed. To set up the periodic updates, youll need to open the
Application section of the manifest file. In the Tile update section, you can define the update
recurrence (from every half hour to daily) and the URL of the XML file with the tiles definition.
The second way to configure periodic updates is by using code. The TileUpdater class offers
a method called StartPeriodicUpdate(), which requires the URL of the XML file and one of
the values of the PeriodicUpdateRecurrence enumerator to define the recurrence.
private void OnSetTileUpdateClicked(object sender, RoutedEventArgs e)
{
TileUpdater manager =
TileUpdateManager.CreateTileUpdaterForApplication();
Uri url = new Uri("http://www.qmatteoq.com/tile.xml", UriKind.Absolute);
manager.StartPeriodicUpdate(url, PeriodicUpdateRecurrence.Hour);
}
One of the advantages of managing periodic notifications in code is that we can implement
scenarios that are not supported by the manifest file. For example, we can set multiple URLs for
the update; this way, at every recurrence, instead of using the same URL, the application will
cycle between URLs and pick the next one in the list. To achieve this goal, we need to call the
StartPeriodicUpdateBatch() method, passing as parameter a collection of Uri objects, like
in the following sample:
private void OnSetTileUpdateClicked(object sender, RoutedEventArgs e)
{
TileUpdater manager =
TileUpdateManager.CreateTileUpdaterForApplication();
List<Uri> uris = new List<Uri>
{
new Uri("http://www.qmatteoq.com/tile.xml", UriKind.Absolute),
new Uri("http://www.qmatteoq.com/tile2.xml", UriKind.Absolute),
new Uri("http://www.qmatteoq.com/tile3.xml", UriKind.Absolute)
};
manager.StartPeriodicUpdateBatch(uris, PeriodicUpdateRecurrence.Hour);
}
Another operation that we can perform with the TileUpdater class is to stop receiving periodic
updates, by calling the StopPeriodicUpdate() method:
private void OnStopTileUpdatesClicked(object sender, RoutedEventArgs e)
{
TileUpdater manager =
TileUpdateManager.CreateTileUpdaterForApplication();
manager.StopPeriodicUpdate();
170
Secondary tiles
Windows and Windows Phone offer a way to create multiple tiles on the Start screen, which
belong to the same application. They are used mostly for two scenarios:
To provide quick access to inner sections of the application. For example, a newsreader
application could provide a way for the user to pin a tile for each news category on his
Start screen. When the user taps on this tile, the application will be opened directly on
the list of news items that belong to that category.
To provide independent tile updates. By expanding the previous sample, well be able to
update a secondary tile connected to a specific news category only with the title of these
news items.
In addition, secondary tiles are synchronized using the Microsoft Account of the user, like the
roaming folder we saw in Chapter 5. This way, if the user has installed the application on
multiple devices, she will automatically find all the secondary tiles she created on her Start
screen. To create multiple tiles, were going to use the same APIs on both platforms. However,
theres an important user experience difference between Windows and Windows Phone:
Windows requires a confirmation from the user before creating the title: a pop-up window
will show a tile preview, and the user will have to press the OK button to add it to the
Start screen.
Windows Phone doesnt require any confirmation, but the application is suspended when
the tile is created, with the Start screen automatically focused on the new tile.
Both scenarios have been implemented to avoid having an application create an unlimited
number of tiles on the users Start screen without approval.
Secondary tiles are mapped with the SecondaryTile class, like in the following sample:
private async void OnCreateSecondaryTileClicked(object sender,
RoutedEventArgs e)
{
string tileId = "SecondaryTile";
string shortName = "Tile";
string displayName = "This is a secondary tile";
string arguments = "science";
Uri logo = new Uri("ms-appx:///Assets/wplogo.png", UriKind.Absolute);
SecondaryTile tile = new SecondaryTile(tileId, shortName, displayName,
arguments, TileOptions.ShowNameOnLogo, logo);
bool isPinned = await tile.RequestCreateAsync();
}
When we create a new SecondaryTile object, we need to specify:
171
The tile name (which should be provided both in short and in full length).
The activation parameters, which are passed to the launching event to identify which tile
has been used, so that we can redirect the user to the proper page and display the
required information.
The image to use for the tile.
The tile is created using the RequestCreateAsync() method, which will return a Boolean value
to notify you of the status of the operation. However, this value is useful only on Windows, since
the user has the chance to abort the operation by dismissing the confirmation pop-up window.
The SecondaryTile class also offers a way to deeply customize the tiles content, thanks to the
VisualElements property, which offers a way to interact with the same properties weve seen
in the manifest file, like the supported tile sizes, the background color, the text, etc.
private async void OnCreateSecondaryTileClicked(object sender,
RoutedEventArgs e)
{
string tileId = "SecondaryTile";
string shortName = "Tile";
string displayName = "This is a secondary tile";
string arguments = "15";
Uri logo = new Uri("ms-appx:///Assets/wplogo.png", UriKind.Absolute);
SecondaryTile tile = new SecondaryTile(tileId, shortName, displayName,
arguments,
TileOptions.ShowNameOnLogo, logo);
tile.VisualElements.BackgroundColor = Colors.Red;
tile.VisualElements.ForegroundText = ForegroundText.Light;
tile.VisualElements.Wide310x150Logo = new Uri("ms-appx:///Assets/widewplogo.png",UriKind.Absolute);
bool isPinned = await tile.RequestCreateAsync();
}
Updating a secondary tile using a notification
Secondary tiles can also be updated using notifications. The required code is the same weve
seen for the main tile. The only difference is that, this time, to create the TileUpdater object,
were going to use the CreateTileUpdaterForSecondaryTile() method. It requires as
parameter the unique identifier of the tile we want to update (which is the first parameter we
passed when we created the SecondaryTile object).
After that, we can proceed as usual to create the notification starting from one of the templates,
and then immediately update the tile (using the Update() method), schedule the notification
(using the AddToSchedule() method), or set the periodic update (using the
StartPeriodicUpdate() method). The following sample shows how to update a secondary
tile:
private void OnUpdateTileClicked(object sender, RoutedEventArgs e)
172
{
var template =
TileContentFactory.CreateTileSquare150x150PeekImageAndText01();
template.TextHeading.Text = "Title";
template.TextBody1.Text = "Text 1";
template.TextBody2.Text = "Text 2";
template.TextBody3.Text = "Text 3";
template.Image.Src = "ms-appx:///Assets/wplogo.png";
TileNotification notification = template.CreateNotification();
string tileId = "SecondaryTile";
TileUpdater updater =
TileUpdateManager.CreateTileUpdaterForSecondaryTile(tileId);
updater.Update(notification);
}
Badge notifications
Badge notifications are connected to tiles; they are symbols that can be displayed over the tile
to draw the users attention. There are two kind of badges:
173
Numbers
Symbols, to warn the user that something happened in the application that requires his
attention.
174
Its enough to create one of these two objects (passing as parameter the number or the symbol
to display) and then call the CreateNotification() method to turn the template into a
BadgeNotification object, which can be sent using the BadgeUpdater class.
private void OnUpdateBadgeClicked(object sender, RoutedEventArgs e)
{
BadgeNumericNotificationContent template = new
BadgeNumericNotificationContent(7);
BadgeNotification notification = template.CreateNotification();
BadgeUpdater updater =
BadgeUpdateManager.CreateBadgeUpdaterForApplication();
updater.Update(notification);
}
If we need to send a symbol badge, the library offers a useful enumerator called GlyphValue,
with a list of all supported symbols. The following sample shows how to send a badge
notification with the attention symbol:
private void OnUpdateBadgeClicked(object sender, RoutedEventArgs e)
{
BadgeGlyphNotificationContent template = new
BadgeGlyphNotificationContent(GlyphValue.Attention);
BadgeNotification notification = template.CreateNotification();
BadgeUpdater updater =
BadgeUpdateManager.CreateBadgeUpdaterForApplication();
updater.Update(notification);
}
175
Numeric badge notifications: We can display the same counter on the tile and on the
lock screen. You can have up to five applications on the Windows Phone lock screen,
and up to seven on the Windows lock screen.
Text notifications: The text that is displayed as title of the wide template can also be
displayed on the lock screen. You can have only one application of this type.
To support lock screen interaction, the first step is to edit the Application section of the
manifest file. In the Notifications section, youll find a dropdown menu called Lock screen
notifications. Youll be able to choose if you support Badge notifications (the numeric value) or
Badge and Tile text notifications (both of them). As soon as you enable one of the two options,
youll see an error icon displayed in the Visual Assets section of the manifest Its required, in
fact, to also set the logo to use in the lock screen, near the number, in the Badge logo section.
Youll have to use a white image that is 24x24 pixels, with a transparent background. You cant
use colors in a lock screen icon.
On Windows, youll also get an error in the Declarations section of the manifest: Windows
applications, in fact, are required to be connected to a background task, which type should be
Timer, Location, Control channel, or Push Notification.
Its important to highlight that the user has full control over which lock screen notifications to
display. We will always able to send them, but its the user that needs to define how many, and
which applications to display on the lock screen.
176
Push notifications
All the samples weve seen in the previous section are useful when the notifications are sent
directly by the application or a background task. However, in some cases we cant rely on these
scenarios. For example, we need to support real-time notifications, but background tasks have
some constraints that make them not suitable for this scenario.
Push notifications are the answer to this problem, since they rely on an external service that
sends the notifications, like a website, a desktop application, or a cloud service. When a specific
event occurs, the service will send a notification to the device, which will receive and process it.
To improve reliability and security, a service cant directly send a notification to the device, but it
needs to use a service offered by Microsoft, called Windows Notification Service (WNS). WNS
is able to gather all the notifications and to redirect them to the proper device.
What is the push notification workflow? You can see a summary in the following image:
When a Windows Store application registers with the WNS, it receives a URL that univocally
identifies the push notification channel. Its a standard HTTP URL, which is exposed on Internet
(here a real channel sample:
https://db3.notify.windows.com/?token=AgYAAABbCcIgWtH2CggvXoBateheW%2b0tKoEYCv1
XIl16GrBjNFUta4S4YT1JhEOCh6JKij4CuSOXOcq%2b4auoEw8chyp%2fHzogm7ieUMzWc9W
CZEK2age%2fR1Zl3CTh0rKFsZE1pEE%3d).
Now it will be easier to understand why all the notifications weve seen so far are identified by
an XML file. Sending a push notification simply means preparing a HTTP request with, as
content, the XML definition of the notification and sending it, using a POST operation, to the
channels URL.
The typical push notification workflow is:
177
One of the most important advantages of this architecture is that its based on standard
technologies, like HTTP or XML. Consequently, we are not forced to use a Microsoft technology
to create our backend, but we can easily integrate a push notification service for Windows Store
applications in our already existing backend service, no matter if its written in Java, PHP, or
Node.js.
178
Figure 23: The Visual Studios screen to reserve a name for the application
Now youre ready to start the submission. Go to the website http://dev.windows.com, log in with
your Microsoft Account, and click on the Dashboard link. Youll be asked which Dev Center you
want to use: choose the Windows Store. Once youve landed on the Windows Dev Center, start
the submission procedure by clicking on the Submit new app link. The procedure will start by
displaying all the required steps to complete the submission. Well ignore them for the moment,
and just complete the minimum set of steps required to get the authentication credentials for the
push channel.
The first step is called App name, and its used to define the application name. Since we
already did this operation in Visual Studio, we can just choose it from the dropdown menu. After
completing this step, well return to the list; lets jump directly to Step 3, called Services. Its the
one we need to set up the push notifications channel. Since were using our own backend, we
need to click If you have an existing WNS solution or need to update your current client
secret, visit the Live Service site. Finally, youll land on a page that will display the information
youre looking for, which is the Package SID and the Client Secret, as displayed in the
following image.
179
Figure 24: The required information to set up authentication for push notifications
Now that we have this information, we can start to write some code. To perform authentication,
we need to perform a POST request with a package that contains the Package SID and the
Client Secret we previously retrieved. The following sample shows how to perform a proper
authentication:
private async void OnAuthenticateClicked(object sender, RoutedEventArgs e)
{
string sid = "ms-app://s-1-15-2-3408799042-3494098530-1890876074596078043-4000267856-3993638234-2202691549";
string secret = "x67C1cf9gxwTi9d3CCzI5CEM/bji31F1";
string encodedSid = HttpUtility.UrlEncode(sid);
string encodedSecret = HttpUtility.UrlEncode(secret);
string body =
string.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&
scope=notify.windows.com", encodedSid, encodedSecret);
HttpClient client = new HttpClient();
StringContent content = new StringContent(body);
content.Headers.ContentType = new MediaTypeHeaderValue("application/xwww-form531urlencoded");
HttpResponseMessage response = await
client.PostAsync("https://login.live.com/accesstoken.srf", content);
}
The content of the HTTP request is stored inside the variable called body. Its a string with a set
of parameters:
180
Since the package contains only text, we can define the content using a StringContent object.
The only special configuration to highlight is that we need to set the ContentType of the request
to application/x-www-form531urlencoded.
Now we can send the request using the PostAsync() method of the HttpClient class to the
URL https://login.live.com/accesstoken.srf, passing as parameter the StringContent object
weve previously defined. If we did everything correctly, well get in return a JSON response with
the access token that will be needed to perform all the other operations with the WNS. This is a
sample JSON response:
{
"token_type":"bearer",
"access_token":"EgAaAQMAAAAEgAAAC4AAzqwf/hYpeD3oG5Lj9SGc53j5U1OtVV30jwHasHVBr
xTWcUF1
zy/1pNnyn2OUUh+bCjs690ELAHQAgH7O94l6EKWoX0oK0oAIf1E0dUk3pf8kRw6VuddENz8n00JZQ
tpNlS3H
SdbvEAugraYKiYfp+Uvi677rSS7/HPhu4tvXN3KJAFoAiQAAAAAAQcURTJ18jFOdfIxT60gEAAsAO
TMuMzYu
MC40MQAAAAAAXgBtcy1hcHA6Ly9zLTEtMTUtMi0zNDA4Nzk5MDQyLTM0OTQwOTg1MzAtMTg5MDg3N
jA3NC01
OTYwNzgwNDMtNDAwMDI2Nzg1Ni0zOTkzNjM4MjM0LTIyMDI2OTE1NDkA",
"expires_in":86400
}
The following compete sample shows how to send the authentication request, and how, by
using the Json.NET library we talked about in Chapter 6, we are able to retrieve the access
token:
private async void OnAuthenticateClicked(object sender, RoutedEventArgs e)
{
string sid = "ms-app://s-1-15-2-3408799042-3494098530-1890876074596078043-4000267856-3993638234-2202691549";
string secret = "x67C1cf9gxwTi9d3CCzI5CEM/bji31F1";
string encodedSid = HttpUtility.UrlEncode(sid);
string encodedSecret = HttpUtility.UrlEncode(secret);
string body =
string.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&
scope=notify.windows.com", encodedSid, encodedSecret);
HttpClient client = new HttpClient();
StringContent content = new StringContent(body);
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-
181
wwwform-urlencoded");
HttpResponseMessage response = await
client.PostAsync("https://login.live.com/accesstoken.srf", content);
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
JObject jObject = JObject.Parse(json);
string accessToken =
jObject.GetValue("access_token").Value<string>();
}
}
If the request is successful (we check the IsSuccessStatusCode property of the response), we
read the responses content. Since its a string, we use the ReadAsStringAsync() method
offered by the Content property. Then, using the JObject class of the JSON.NET library, we
are able to retrieve the value of the access_token element. Its important to save this
information locally, since were going to include it in every request we will use to send a push
notification. The access token is valid for 24 hours; after this period, we will have to repeat the
authentication procedure to acquire a new one.
Now we have all the information required to send a push notification. Lets see how to compose
the HTTP request that defines a notification:
The request content is the XML file that defines the notification, according to what weve
seen in this chapter.
The request should contain a header called X-WNS-Type, with the type of notification that
we want to send. The supported values are:
o wns/toast for toast notifications
o wns/tile for tile notifications
o wns/badge for badge notifications
o wns/raw for raw notifications
The content type of the request is different according to the notifications type: for toast,
tile, and badge notifications, its text/xml; for raw notifications, its
application/octect-stream.
We need to specify the size of the content, by setting the ContentLength header.
We need to include a header called Authorization, with the access token weve
previously retrieved.
The following sample shows how to send a toast notification by using the
ToastImageAndText01 template:
private async void OnSendNotificationClicked(object sender, RoutedEventArgs
e)
{
string xml =
String.Format(
"<?xml version='1.0' encoding='utf-8'?><toast><visual><binding
182
Since the notifications content is a string, we define a StringContent object with the XML
template as body. Then we add the required header: the notification type (wns/toast, in this
case), the content type (text/xml), and the content length (which is the length of the XML file).
We also set the Authorization header with a new AuthenticationHeaderValue object,
which requires as parameter the authentications type (which is Bearer) and the previously
retrieved access token. The last step is to define the channel URL in the RequestUri property.
In this sample, were assuming that our WPF application has a TextBox control where the user
can manually insert the channel URL.
In the end, we send the notification by calling the SendAsync() method and passing as
parameter the HttpRequestMessage object weve just defined. In return, we will get an
HttpResponseMessage object, which can be used to determine the operation status. The status
can be identified by the StatusCode property, which contains the HTTP status code of the
response. The most common values we can get in return are:
You can find a complete list of all the error codes in the MSDN documentation at
http://s.qmatteoq.com/PushStatus.
183
184
{
string id = "MySecondaryTile";
PushNotificationChannel channel = await
PushNotificationChannelManager.CreatePushNotificationChannelForSecondaryTileA
sync(id);
}
catch (Exception exc)
{
Debug.WriteLine("Error creating the channel");
}
}
185
case PushNotificationType.Tile:
content = args.TileNotification.Content.GetXml();
break;
case PushNotificationType.Badge:
content = args.TileNotification.Content.GetXml();
break;
case PushNotificationType.Raw:
content = args.RawNotification.Content;
break;
}
args.Cancel = true;
}
In the event handler, we get a parameter with a property called NotificationType, which
contains one of the values of the PushNotificationType enumerator. We can use it to
discover which kind of notification has been received. The parameter also contains a property
for each notifications type; once weve identified which notification we have received, we can
get its content by using the Content property. When it comes to toast, tile, or badge
notifications, we can simply retrieve the XML template by using the GetXml() method. Since
raw notifications dont have a fixed structure, we simply retrieve the value of the Content
property.
Intercepting a push notification with the PushNotificationReceived method doesnt prevent
the system from managing it. It means that, even if the application is opened, a toast banner will
be displayed, or the tile will be updated. If we want to avoid this behavior (for example, if the
user is already using the app, it doesnt make sense to show a toast notification), we can simply
set the Cancel property of the handlers parameter to true.
186
187
Triggers: A specific event can trigger a background task. The Windows Runtime offers
many trigger types, like system events, push notifications, timers, etc.
Conditions: In this case, a background task can be executed only when a set of specific
conditions is satisfied. For example, if the task requires an Internet connection to work
properly, we can add a specific condition to make sure that its executed only when the
Internet is available.
Consequently, an application is able to register multiple background tasks, which are connected
to different triggers.
Triggers
As already mentioned, triggers are the events that can invoke a background task. Here is the list
of supported triggers:
SystemTrigger: These triggers are connected to system events, like a change in the
network connectivity, the lock screen activation, etc.
TimeTrigger: These triggers are periodically executed. The minimum time interval
between one execution and the next is 15 minutes for Windows, and 30 minutes for
Windows Phone.
LocationTrigger: These triggers are connected to the geofence APIs we saw in
Chapter 7. They are triggered every time the user enters or exits from an area.
MaintenanceTrigger: These are similar to the TimeTrigger category, but they used
for time-consuming operations. Consequently, they are executed only when the device is
connected to a power source.
188
Additionally, there is a set of triggers that are used to interact with external devices, like
RfcommConnectionTrigger (when a connection is established using the RFCOMM protocol),
DeviceChangeTrigger (when the connection with a device is established or closed),
BluetoothSignalStrengthTrigger (when the strength of the Bluetooth signal changes) or
GattCharacteristicNotificationTrigger (used to interact with Bluetooth Low Energy
devices).
Conditions
Conditions are used to trigger a background task only when one or more requirements are
satisfied. They are not required like triggers (when you register a background task, you must
specify the triggers type, but conditions are optional), but they are useful to avoid wasting
resources and executing a task when we know, in advance, that the operation cant be
completed successfully.
Conditions are represented by the values of an enumerator called SystemConditionType. Here
are the main values:
189
UserPresent / UserNotPresent: This condition detects if the user is using the device.
InternetAvailable / InternetNotAvailable: This condition detects if the device is
connected to the Internet.
SessionConnected / SessionDisconnected: This condition detects if theres an active
session.
FreeNetworkAvailable: This condition detects if the device is connected to Internet
using a free network (like the office or home network), and not a paid connection (like a
public hotspot).
CPU usage
The background can use the CPU for a limited time:
One second for Windows applications that are not integrated with the lock screen.
Two seconds for Windows applications that are integrated with the lock screen.
Two seconds for Windows Phone applications.
Its important to highlight that these timings refer only to the CPU time, and not to the total task
time. For example, if your background tasks download some data from Internet, the download
time is not counted against this constraint. CPU is used only when the data has been download
and needs to be processed.
Network usage
Another constraint is related to network usage, even if these limitations are applied only when
the device isnt connected to a power source. The constraints change depending on whether or
not the application supports lock screen integration:
Type
Daily usage
0.469 MB
45 MB
Standard application
0.625 MB
7.5 MB
Its important to highlight that these parameters are not fixed, but change according to the
average speed of the current network connection. The values displayed in the previous table
refer to a 1 Mbit connection; real values need to be multiplied for the average speed of your
connection. For example, if the device has a 10 Mbit connection, youll have to multiply the
previous values by ten (so the maximum daily usage for a lock screen application would be 450
MB, and not 45 MB).
190
512 MB devices
1 GB devices
2 GB or more devices
Location
16
30
40
Bluetooth
16
16
16
16
30
40
Debugging limits
30
40
50
If you want to track the memory usage of a background task in real time, you can use a class
called MemoryManager, which belongs to the Windows.System namespace. However, this class
can be used only in a Windows Runtime Component specific to Windows Phone, since its not
available on Windows. The MemoryManager class offers a property called AppMemoryUsage,
which contains the current amount of memory used. The following sample shows how to display
this information in the Visual Studio Output Window:
private void OnCheckMemoryClicked(object sender, RoutedEventArgs e)
{
Debug.WriteLine(MemoryManager.AppMemoryUsage.ToString());
}
191
192
We get a BackgroundTaskDeferral object by using the GetDeferral() method. After all the
operations have been completed, we call the Complete() method, so that the task can be
terminated.
Set, in the Properties section, which kind of triggers you want to support.
Set, in the Entry point section, the full qualifier of the background task, which is the full
namespace with the class name. For example, if we have created a project called
MyBackgroundTask and weve defined the task in a class called BackgroundTask, the
full qualifier would be MyBackgroundTask.BackgroundTask.
As we already mentioned, when you add a background task in Windows and you enable a
specific trigger type, the manifest editor will display some errors, since you need to properly
configure the lock screen integration.
Now that the manifest is properly configured, we can register the background task in code using
the BackgroundTaskBuilder class. Typically, this operation is performed when the application
starts.
private void OnRegisterTaskClicked(object sender, RoutedEventArgs e)
{
string taskName = "Test task";
bool isTaskRegisterd = BackgroundTaskRegistration.AllTasks.Any(x =>
x.Value.Name
== taskName);
if (!isTaskRegisterd)
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = taskName;
builder.TaskEntryPoint = "BTask.BackgroundTask";
builder.SetTrigger(new SystemTrigger(SystemTriggerType.UserAway,
false));
builder.AddCondition(new
SystemCondition(SystemConditionType.InternetAvailable));
BackgroundTaskRegistration taskRegistration = builder.Register();
}
}
193
Since we cant register two background tasks with the same name, the first step is to check if
the task is already registered in the system. We can do this by enumerating the AllTasks
collection exposed by the BackgroundTaskRegistration class, and looking for a task with the
name we have chosen as an identifier (in the previous sample, its Test task).
If the task isnt already registered, we create a new BackgroundTaskBuilder object and define
the main properties: Name (the identifier) and TaskEntryPoint (which is the same entry point
weve specified in the manifest). Then we need to define the kind of trigger we want to associate
to the task, which should be the same as weve defined in the manifest. We call the
SetTrigger() method and by passing, as parameter, an IBackgroundTask object, which is
the base interface that is implemented by every trigger in the Windows Runtime. In the previous
sample, we register a SystemTrigger, specifying as type the UserAway value of the
SystemTriggerType enumerator. Well see more details about using system triggers later in the
chapter.
Conditions are added using the AddCondition() method, which requires as parameter a
SystemCondition object. The condition is defined by using one of the values of the
SystemConditionType enumerator. Now that the task definition is completed, we can register it
in the system by calling Register() method.
194
}
The base code is the same, but before calling the Register() method, we check the value of the
BackgroundStatus enumerator returned by the RequestAccessAsync() method. We proceed
with the registration only if we get a value different than Denied, which is the value that we get if
the user has denied the registration.
Figure 26: The pop-up message that requires permission from the user to register the background task
Its important to highlight that the previous code should be used only when the background task
is using of the triggers that requires lock screen integration. If you call the
RequestAccessAsync() method to register a standard background task, youll get an
exception.
195
196
Figure 27: The Visual Studio option to manually trigger a background task
197
System trigger
System triggers can be used to execute a background task when something changes in the
system status. Here is a list of all the available triggers:
UserPresent / UserAway: Its triggered when the user stops using the device or
resumes using it after a while. Typically, its executed when the device is locked or
unlocked. In Windows, this trigger requires us to enable the lock screen integration.
NetworkStateChange: Its triggered when the connectivity status changes.
ControlChannelReset: Its triggered when the TCP/IP channel is reset. In Windows, In
Windows, this trigger requires us to enable the lock screen integration.
InternetAvailable: Its triggered when the device is connected to Internet.
LockScreenApplicationAdded / LockScreenApplicationRemove: This trigger is
available only in Windows, and its executed when the application is added or removed
from the lock screen.
TimeZoneChange: iIs triggered when the time zone of the current device is changed.
The trigger is invoked only when the time zone change causes the current time to shift.
The following sample shows how to register a background task that is connected to a system
trigger, in this case InternetAvailable:
private async void OnRegisterTaskClicked(object sender, RoutedEventArgs e)
{
string taskName = "Test task";
bool isTaskRegisterd = BackgroundTaskRegistration.AllTasks.Any(x =>
x.Value.Name== taskName);
if (!isTaskRegisterd)
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = taskName;
builder.TaskEntryPoint = "BTask.BackgroundTask";
builder.SetTrigger(new
SystemTrigger(SystemTriggerType.InternetAvailable, false));
BackgroundAccessStatus status = await
BackgroundExecutionManager.RequestAccessAsync();
if (status != BackgroundAccessStatus.Denied)
{
BackgroundTaskRegistration task = builder.Register();
}
}
}
The previous code can register, for example, a background task that (using the APIs we saw in
Chapter 10 and the NotificationsExtensions library) sends a toast notification every time an
Internet connection is detected:
public sealed class BackgroundTask : IBackgroundTask
198
{
public void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
ToastNotifier notifier =
ToastNotificationManager.CreateToastNotifier();
var toast = ToastContentFactory.CreateToastText01();
toast.TextBodyWrap.Text = "The device is connected to Internet";
ToastNotification notification = toast.CreateNotification();
notifier.Show(notification);
deferral.Complete();
}
}
Time trigger
Time trigger tasks are used to perform operations that should be performed periodically while
the device is turned on. For example, a newsreader application could use a background task to
periodically check for the latest news items. When you register a time trigger task using the
TimeTrigger class, youll need to provide two parameters:
The second one is the trigger type: one shot (the task is executed just once) or
traditional (the task will be periodically executed).
The first one is a time value, and its connected to the trigger type. If its a one-shot
trigger, it defines how much time must pass before the task is executed. If its a standard
time trigger, it defines the interval between one execution and the next. Its important to
remember that this value can be more than 15 minutes for Windows, and more than 30
minutes for Windows Phone.
The following sample is used to register a background task that is executed every 60 minutes:
private async void OnRegisterTaskClicked(object sender, RoutedEventArgs e)
{
string taskName = "Test task";
bool isTaskRegisterd = BackgroundTaskRegistration.AllTasks.Any(x =>
x.Value.Name == taskName);
if (!isTaskRegisterd)
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = taskName;
builder.TaskEntryPoint = "BTask.BackgroundTask";
builder.SetTrigger(new TimeTrigger(60, false));
builder.AddCondition(new
SystemCondition(SystemConditionType.InternetAvailable));
BackgroundAccessStatus status = await
BackgroundExecutionManager.RequestAccessAsync();
if (status != BackgroundAccessStatus.Denied)
{
BackgroundTaskRegistration task = builder.Register();
199
}
}
}
In the previous sample weve also added a condition, by using the AddCondition() method:
the task will be executed only if an internet connection is available. To show a background task
sample connected to a time trigger, lets reuse the newsreader app sample. The following task,
by using the SyndicationClient class we saw in Chapter 5, retrieves the number of news
items from an RSS feed and displays it on the main tile using a badge.
public sealed class BackgroundTask : IBackgroundTask
{
public async void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
SyndicationClient client = new SyndicationClient();
SyndicationFeed feed = await client.RetrieveFeedAsync(new
Uri("http://feeds.feedburner.com/qmatteoq", UriKind.Absolute));
int numberOfArticles = feed.Items.Count;
BadgeNumericNotificationContent content = new
BadgeNumericNotificationContent();
content.Number = (uint) numberOfArticles;
BadgeNotification badgeNotification = content.CreateNotification();
BadgeUpdater updater =
BadgeUpdateManager.CreateBadgeUpdaterForApplication();
updater.Update(badgeNotification);
deferral.Complete();
}
}
Theres also another kind of time trigger, called MaintenanceTrigger; its periodically
executed, like the TimeTrigger, but only when the device is connected to a power source.
Consequently, unlike the TimeTrigger, it doesnt require interaction with the lock screen. If the
device is disconnected from the power source while the task is running, it will be interrupted,
and it will restart only when it is reconnected. The following sample defines a background task
using a MaintenanceTrigger that is executed every 60 minutes:
private async void OnRegisterTaskClicked(object sender, RoutedEventArgs e)
{
string taskName = "Test task";
bool isTaskRegisterd = BackgroundTaskRegistration.AllTasks.Any(x =>
x.Value.Name == taskName);
if (!isTaskRegisterd)
200
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = taskName;
builder.TaskEntryPoint = "BTask.BackgroundTask";
builder.SetTrigger(new MaintenanceTrigger(60, false));
builder.AddCondition(new
SystemCondition(SystemConditionType.InternetAvailable));
BackgroundAccessStatus status = await
BackgroundExecutionManager.RequestAccessAsync();
if (status != BackgroundAccessStatus.Denied)
{
BackgroundTaskRegistration task = builder.Register();
}
}
}
Geofencing triggers
In Chapter 7, we saw that the Windows Runtime provides a set of APIs to implement
geofencing, which is a way to intercept when the user enters or exits from a specific geographic
area. We also learned how to manage this scenario when the application is running. The same
approach can also be implemented in a background task, so that we can detect when the user
reaches a certain area, and trigger an operation. The following sample shows how to register a
geofencing task in the main application:
private async void OnRegisterTaskClicked(object sender, RoutedEventArgs e)
{
string taskName = "Test task";
bool isTaskRegisterd = BackgroundTaskRegistration.AllTasks.Any(x =>
x.Value.Name == taskName);
if (!isTaskRegisterd)
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = taskName;
builder.TaskEntryPoint = "BTask.BackgroundTask";
builder.SetTrigger(new
LocationTrigger(LocationTriggerType.Geofence));
BackgroundAccessStatus status = await
BackgroundExecutionManager.RequestAccessAsync();
if (status != BackgroundAccessStatus.Denied)
{
BackgroundTaskRegistration task = builder.Register();
}
}
}
201
Its enough to create a new LocationTrigger object, passing as parameter the value
Geofence of the LocationTriggerType (which is the only available one).
Inside the background task, we can reuse the code from Chapter 7:
public sealed class BackgroundTask : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
var reports = GeofenceMonitor.Current.ReadReports();
foreach (GeofenceStateChangeReport report in reports)
{
GeofenceState state = report.NewState;
string status = string.Empty;
switch (state)
{
case GeofenceState.Entered:
status = "The user entered the area";
break;
case GeofenceState.Exited:
status = "The user exited from the area";
break;
}
IToastText01 toast = ToastContentFactory.CreateToastText01();
toast.TextBodyWrap.Text = status;
ToastNotification notification = toast.CreateNotification();
ToastNotifier notifier =
ToastNotificationManager.CreateToastNotifier();
notifier.Show(notification);
}
deferral.Complete();
}
}
Compared to the code from Chapter 7, there are two main differences:
202
In the main application, we displayed a pop-up message to notify the user that the event
was triggered. In this scenario, since we are in a background task and we cant display a
pop-up message, we send a toast notification using the APIs from Chapter 10.
203
}
This code is just a basic sample; to better understand how to register and handle a notification
channel, see Chapter 10.
Now that the application is configured to receive notifications, every time the backend sends a
raw notification, the system will run the background task weve just registered. Lets take a look
at a background tasks sample code:
public sealed class BackgroundTask : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
RawNotification rawNotification = taskInstance.TriggerDetails as
RawNotification;
IToastText01 toast = ToastContentFactory.CreateToastText01();
toast.TextBodyWrap.Text = rawNotification.Content;
ToastNotification toastNotification = toast.CreateNotification();
ToastNotifier notifier =
ToastNotificationManager.CreateToastNotifier();
notifier.Show(toastNotification);
deferral.Complete();
}
}
When the background task is triggered by a raw notification, its content is stored in the
TriggerDetails property of the IBackgroundTaskInstance parameter. However, since
TriggerDetails is a generic object (it can also be used for other scenarios), it first needs to be
converted into a RawNotification object.
Now we can access the notifications content, thanks to the Content property. In the previous
sample, we take this content and display it to the user with a toast notification.
204
Its not a trigger type. To register it in the manifest file, we wont add the Background
Tasks item in the Declarations section, but rather the Update Task item. However,
well have to set the Entry point field with the full qualifier of the class that implements
the IBackgroundTask interface.
Its automatically registered using the manifest declaration; it doesnt required us to
register it using the BackgroundTaskBuilder class.
Except for these two differences, it behaves like a regular task; youll have to include, in the
Run() method, the code needed to update or migrate your data.
205