Exercise

In this exercise, we show how to add secondary tiles representing specific projects to the main screen. We then create a background agent to update the pinned project tiles.

Task 1 – Pinning Project Tiles to the Start Area

  1. Open the starter solution located in the lab installation folder under Source\Begin.
  2. Locate the Todo.Business project and create a new class named ShellTileHelpersCore under the Shell project folder. Make the class static:

    C#

    public static class ShellTileHelpersCore { // … }

  3. Add the following using statements to the newly created class file:

    C#

    using Microsoft.Phone.Shell; using System.Linq;

  4. This class helps us to encapsulate tile pinning and unpinning functionality. Create a Pin method that will allow us to pin tiles to the device’s start area:

    C#

    public static void Pin(Uri uri, ShellTileData initialData) { // Create the tile and pin to start. This will cause the app to be // deactivated ShellTile.Create(uri, initialData); }

    ShellTile is a class from the Microsoft.Phone.Shell namespace, which is responsible for managing primary and secondary tiles for the application. The class provides a number of static methods, which helps to create/remove tiles and access a collection containing all of the application’s tiles, which can be used to look for specific tile. Each application tile has to specify the navigation URI to follow when the user taps the tile on the main screen as well as additional data in the form of a ShellTileData instance, which defines the tile’s look.

    Note:
     We examine the ShellTileData class and its properties later in this task.

  5. Add two overloads for an UnPin method using the following code snippet:

    C#

    public static void UnPin( string id ) { var item = ShellTile.ActiveTiles.FirstOrDefault (x => x.NavigationUri.ToString().Contains ( id ) ); if (item != null) item.Delete(); } public static void UnPin( Uri uri ) { var item = ShellTile.ActiveTiles.FirstOrDefault (x => x.NavigationUri == uri); if ( item != null ) item.Delete (); }

    To remove a pinned tile we need to locate it first, and then execute Delete function on THE found tile. Both methods use the ShellTile.ActiveTiles property to try to locate the specified tile. If the tile is located, we delete it.

  6. Add two additional methods to the class. These will be similar in nature to the UnPin method but will be used to check whether a certain project has a tile in place or not:

    C#

    public static bool IsPinned(Uri uri) { var item = ShellTile.ActiveTiles.FirstOrDefault (x => x.NavigationUri == uri); return item != null; } public static bool IsPinned( string uniqueId ) { var item = ShellTile.ActiveTiles.FirstOrDefault (x => x.NavigationUri.ToString().Contains(uniqueId)); return item != null; }

  7. Save the file and navigate to the Todo project. Locate the project folder named Push and add a new class named ShellTileHelpersUI under it. Make the class static and make sure it is a part of the Todo namespace (by default it is created under the Todo.Push namespace):

    C#

    namespace Todo { public static class ShellTileHelpersUI { // … } }

    This class will use functionality we introduced in ShellTileHelpersCore to help manage Pin/Unpin functionality through the UI.

  8. Add the following using statements to the newly created class file:

    C#

    using Microsoft.Phone.Controls; using Microsoft.Phone.Shell; using System.Linq; using System.Windows.Media.Imaging; using System.IO.IsolatedStorage; using Todo.Misc; using Todo.Shell; using Todo.Resources;

  9. Our aim is to create tiles that represent a single project each and provide a bit of information about the project at a glance. As mentioned before, each tile should have a URI leading to a page displaying its associated project. Add the following extension method to easily create a URI from a given project:

    C#

    public static Uri MakePinnedProjectUri(this Project p) { return UIConstants.MakePinnedProjectUri(p); }
    Note:
    To see the implementation for MakePinnedProjectUri, navigate to the UIConstants.cs file under the Misc folder.

  10. Add an additional method to create a URI leading to a tile background image that matches a specified project’s color.

    C#

    public static Uri GetDefaultTileUri (this Project project) { string color = ApplicationStrings.ColorBlue ; // default to blue ColorEntryList list = App.Current.Resources[UIConstants.ColorEntries] as ColorEntryList ; if (list != null) { ColorEntry projectEntry = list.FirstOrDefault(x => x.Color == project.Color); if (projectEntry != null) color = projectEntry.Name; } return UIConstants.MakeDefaultTileUri(color); }

  11. Add a method to pin a project to the start area. For this, we require a ShellTileData value and as the class is abstract, we will use the StandardTileData class that derives from it. Create the PinProject method according to the following code:

    C#

    public static void PinProject (Project p) { // Create the object to hold the properties for the tile StandardTileData initialData = new StandardTileData { // Define the tile’s title and background image BackgroundImage = p.GetDefaultTileUri(), Title = p.Name }; Uri uri = p.MakePinnedProjectUri(); ShellTileHelpersCore.Pin(uri, initialData); }

  12. The UnPinProject method will simply pass a project’s ID to the UnPin method we created previously:

    C#

    public static void UnPinProject(Project p) { ShellTileHelpersCore.UnPin(p.Id.ToString() ); }

  13. Similarly, IsPinned will rely on previous ShellTileHelpersCore implementations:

    C#

    public static bool IsPinned ( this PhoneApplicationPage page ) { return ShellTileHelpersCore.IsPinned( page.NavigationService.CurrentSource); } public static bool IsPinned(this Project project) { Uri uri = project.MakePinnedProjectUri(); return ShellTileHelpersCore.IsPinned( project.Id.ToString() ); }

  14. Save the class and navigate to the ProjectDetailsView.xaml.cs file under the Views\Project subfolder. This class already has a method named appBar_OnPinProject. This method is an event handler method, executed when the user taps the application bar pin/unpin icon. Add the following code to the method’s body:

    C#

    private void appBar_OnPinProject(object sender, EventArgs e) { Project project = DataContext as Project; if (project.IsPinned() ) ShellTileHelpersUI.UnPinProject(project); else ShellTileHelpersUI.PinProject( project ); UpdateProjectPinIcons(); }

    This method either pin or unpins the project, depending on its current state, and updates the application bar icon accordingly.

  15. Locate the method named UpdateProjectPinIcons, which is already present in the file. The method is currently empty. Add the following code snippet to initialize the application bar icon and text according to the project’s pinned status:

    C#

    private void UpdateProjectPinIcons()
    FakePre-271b903d1105472d85214eac3d87d80f-30e14ac36fc04a92909795878ffb1526FakePre-54ff66c780384caf9912433baa5767fb-ab22fb5a962d47438a8ac1941f57a5abFakePre-0f7362da90684b048acd4de22e17eea9-8260ad746d9e4f10b3b683741e49531aFakePre-d1f0f2388db44f9aa239480736aab429-41d0b3f6047340f7ac29c70c21e06a27FakePre-cb7e76ecda3c4887b3aca6c98acbc9e3-750ca7b858c0433da61d2928de32207cFakePre-d618ce953cfb4d0496423f8a6a4f3de6-a0c9d7641b314496b7a53734c64dc31fFakePre-683ec6d53c6747248652ba860b77f3b7-f261a1d479f54eefb4672fd0661bd314FakePre-011938e88b0c47a08bee4c3166e6aad8-d83106f571ab4891b5b8ccad9de947a8FakePre-315d46c2048f428e9d03a28f7112b865-ce6e39273a484849ac47c6c53febefe1FakePre-a05c827933924f85ac1cbc396c7a189f-53a00650bb3947b59d85f66e88e38a7bFakePre-899ef9aa29d4406a9d8fae81b9afafed-db1abdc51098476d837841c64dc97169FakePre-d157423fda8a4103b6dfd35f6d6f13ef-ea8acf10622e4220818ee9bb6c80249b

    This code uses the application’s constants to retrieve localized text according to the project pin state.

  16. Locate the InitializePage method and add the highlighted code in the snippet below to the end of the method:

    C#

    private void InitializePage()
    FakePre-401ab805055f4a45847603f8a0381224-de40cffba0444b5abcf9a2d72ea610d9FakePre-97bc66189e664651a68ae077b5b6c6cd-9a3f54b7db11465b9684141e45481272FakePre-d0db625b3d614a6496df4394306e7a1e-9dc79a8b47174e7594a98f2473bf5fc8FakePre-9803ea6d0fb84182b5c146cd3588ffe9-b87adb669cc54a8eb3869baf53c1e646FakePre-601f7acb63144d35b8d7eb868a17c21a-6dc64193e7604f0591940c35ac4f82d1FakePre-6ee6d9f6ca554d3f9ba2f91d7ca81f27-3e0ada0c173a40ab97e7962e2ba8bd82FakePre-da0c789b2f88415eae7ec016bfb98882-985ea4ce404e4658987890d1f103e22dFakePre-ff795a2387364d4e9202e30e814dec36-964996ccf91c4fcba3f165aa1f5f23b2FakePre-3fdd0cdcb9b94098ba2a09e420570416-6b3054420a904e988535fe7b31c73da9FakePre-bcb281ab8bc1412e86a9f0b1b9d38186-bc6a0ccee0254c5f95bd948dd079ff28FakePre-ea712083bc824acfa1d25fb4f5014717-f400350ede7b44e2899181ab7a6415e8FakePre-da26149adf5c402684b450011282167c-7c05ecac4e344281991955f72fe47b34FakePre-94e3088a97d2472dbcd0d532d1932fa3-9b72467a796e496abb193b8d13fceebdFakePre-8a1bb6d902114da481479358ae303c55-2ed20c1aeddb45b59ef2738e2856c2ecFakePre-c0f2dbcbba39458694a4ee63e2a01f1f-761a4277c84947daa3bb31050f3828e2FakePre-bb1fd746980e4fccaf1068d1f72bbc72-4b176502d3b341a090d99440d8b6ee8bFakePre-538e71119a2b44c9a1be36e03c3d598c-15c9f6a1b1fc44bcbd9dc9ad48838d18FakePre-4821165d2f494ff49f407a82f8cc6f9e-23dfd44762884151963f4cdffb9ad2e6FakePre-9ee7cbddd6114de484c1c071b1ecfd4e-6a77e838c40f48d9bb3f954c57534d28FakePre-917b8e125e0544edb693e1d270fdb0c6-b0fb64b8fe664b89b19ee32089af8788FakePre-b7655c9df6314f309baae74df11bb381-1e2ffa9af73a48fcba81eae9495dbe8dFakePre-271a5bcc44f4421ca524b1d587b97f5f-aae6edb6157248708927d6db502f2063FakePre-92203995c79b4f1fb9b6dea6b3138680-6d402ddb307148e6a57d2f2be87962cbFakePre-9c6cd106333c44dcbe1819ee89c225c3-fa4ea7329480481ca64f70feb356b199FakePre-bc5c6269ce15491f8d07f677080245f6-5673bc3aa751437c86e43d9bc10c0f22FakePre-a664598768814239b7b39eec7fe4b615-ed775034a57b4dcb8e3723ae748a5941FakePre-ffe5c3bfa02a481fac086e2aa13976de-3c9947b01b564628a24723832b7061c5FakePre-df9cd2fb65854053a4ead12cde5d6dde-88dd66ff81aa4aeab87aced8a4e40102FakePre-c51bff1d1a11497089c2d2e5cfe2ce2b-5b0628b801a94729b1ed63334d0e6c8bFakePre-ace79c9b46414962b7a8cb54aea69c9c-904d5b67826c4cffa44d5eb20f25e547FakePre-bbc612f90734400d90f693ccd6f15130-cff52f981d8746d3921344b01210fa4cFakePre-d39df5a625134753bee77ba404a08bad-32e7df9872604a11b9dff9cb8fc67af1FakePre-5a89f20915e14c5b8f89ab5dabdb00d1-1b44e6c247dc4833ad0a7741b60c3a99FakePre-a01eeb3f12724150904f86019b710cd3-5a46eaba529e465bbbb861142d49b0aaFakePre-8c64c324cdf44d138afbc8d16b470e66-96ab0a1932fd44b99df499c6307c9eacFakePre-42c61919734444d08610e696d67e2c99-53f38caecb93467cb29a726d7c7e6ea1FakePre-b2e61ec03cac4e259ab345d821bf090c-c34e876027b440e88e135fd33821752bFakePre-697186598d6c4857b8bd51dba20a9b6e-cd2f8b13acc84f59ad0a5f6b379c85e7FakePre-c8a67dff9d774ee9b0c45cb93eed78e7-7fefb8005f194e16b0acc6eaf2bdbc89FakePre-fe4c828347064bd6b54f03ca5f01c9f6-5ffc68cf30b944ed9a1336180b0b2e93FakePre-ebcef272b4814ecf85e76e6f97bc72c3-f06df4454c11499886e9367fcd120ad1FakePre-280459fb0d4c4f5b91cc6d10fb20faa5-c2455987db2847059251336e22f42b28FakePre-ec802406aa74401e84cb198815cbb6ae-bc99bf4e0c33477893762b16baa5318eFakePre-1660be1f99de4ca6aad35344eb4690f7-99e03e3b860a44e0ac5af01485078f63

    This new block of code handles a special case where the application is launched by pressing one of the pinned project tiles. When launching the application through one of the project tiles, the first page the user will see is the associated project’s details. Since the project details page is the first page seen, pressing the device’s back key will back out of the application instead of returning to the main menu. For this reason, when launched through a project pinned tile we will add a special button to the application bar that allows the user to return to the application’s main page.

  17. Save the class, compile and start the application. Navigate to the projects management screen (by tapping the “folder” icon on the main screen application bar) and create at least 2 projects with different colors. Create a few tasks and assign them to the different projects. Your project list should now look like the following screenshots:

    Figure 1

    Projects screen

  18. Tap on project icon to navigate into the project details screen and use the “Pin” icon to pin the project:

    Figure 2

    Project Details Screen

    Figure 3

    Pinned project

  19. Press the pinned tile to reach the project page directly:

    Figure 4

    Project details screen

  20. Now that the project is pinned, see how the “Pin” icon changed. Tap the unpin icon and navigate away from the application – you will see that the project was unpinned:

    Figure 5

    Start Screen

  21. It is possible to pin/unpin multiple projects using the previous steps:

    Figure 6

    Multiple project tiles

  22. This concludes the task. In the next task, we will add a new background agent, which will update the pinned tiles.

Task 2 – Implementing a Background Agent

  1. Add a new project to the solution. Use the “Windows Phone Task Scheduled Agent” template and name it TaskLocationAgent:

    Figure 7

    Adding new project to the solution

  2. Add a reference to this new project from the Todo project:

    Figure 8

    Adding a Reference to the Background Agent

  3. Open the WMAppManifest.xml file from Properties folder in the Todo project and see how adding a reference to an agent project causes the manifest to include an additional element that points to the new agent we added:

    XML

    <ExtendedTask Name="BackgroundTask"> <BackgroundServiceAgent Specifier="ScheduledTaskAgent" Name="TaskLocationAgent" Source="TaskLocationAgent" Type="TaskLocationAgent.ScheduledAgent" /> </ExtendedTask>

  4. Before you implement the agent, add some code to start and stop the agent. Navigate to the SettingsViewModel.cs file under the ViewModels folder. Create the following class members in the SettingsViewModel class:

    C#

    PeriodicTask periodicTask = null; const string PeriodicTaskName = "TidyPeriodic";

    PeriodicTask is a class from the Microsoft.Phone.Scheduler namespace, which represents a scheduled task that runs regularly for a small amount of time. We will use these variables later in this task.

  5. Locate the OnSave method and add the following code before the line that contains “SaveSettings();”:

    C#

    void OnSave( object param ) { if (UseBackgroundTaskUpdates || UseBackgroundLocation) { EnableTask(PeriodicTaskName, ApplicationStrings.PeriodicTaskDescription); } else DisableTask(PeriodicTaskName); SaveSettings(); }

    This method will use two helper methods, which we create in the next steps.

  6. Add the EnableTask method:

    C#

    void EnableTask(string taskName, string description) { PeriodicTask t = this.periodicTask; bool found = (t != null); if (!found) { t = new PeriodicTask(taskName); } t.Description = description; t.ExpirationTime = DateTime.Now.AddDays(10); if (!found) { ScheduledActionService.Add(t); } else { ScheduledActionService.Remove(taskName); ScheduledActionService.Add(t); } if (Debugger.IsAttached) { ScheduledActionService.LaunchForTest(t.Name, TimeSpan.FromSeconds(5)); } }

    This method tries to locate a previously created periodic task and updates its description and expiration time. Otherwise, a brand new periodic task is created. Whether a new task is created or not, a call is placed to launch the agent for debugging purposes if a debugger is attached. The ScheduledActionService class enables the management of scheduled actions.

    Note:
    The task “update” is actually done by deleting the old task and adding a new one instead.

  7. Add the DisableTask method to disable a previously enabled periodic task:

    C#

    void DisableTask(string taskName) { try { PeriodicTask t; if (periodicTask != null && periodicTask.Name != taskName) t = periodicTask; else t = periodicTask = ScheduledActionService.Find(taskName) as PeriodicTask; if (t != null) ScheduledActionService.Remove(t.Name); } finally { }; }

    Like in the previous step, this code looks for an existing task and removes it using the SchduledActionService class.

  8. Modify the SettingsViewModel’s constructor to pick up the periodic task’s status at initialization – modify the constructor according to the following code snippet:

    C#

    public SettingsViewModel() { syncProvider = new LocalhostSync(); syncProvider.DownloadFinished += DownloadFinished; syncProvider.UploadFinished += UploadFinished; syncProvider.DownloadUploadProgress += OperationProgress; periodicTask = ScheduledActionService.Find(PeriodicTaskName) as PeriodicTask; if (periodicTask != null) IsBackgroundProcessingAllowed = periodicTask.IsEnabled; else IsBackgroundProcessingAllowed = true; LoadSettings(); }

  9. Save this class and return to the TaskLocationAgent project. Add a reference to the Todo.Business project in the TaskLocationAgent project.
  10. Navigate to the TaskScheduler.cs file. Let us review this file. It has one method: OnInvoke, called when a periodic task is invoked by the scheduled action service.

    Note:
     This lab will not focus on location updates, which are part of the full application and are performed using this background agent. The end solution for this lab does contain the relevant code, but we will not cover it as part of the lab.

  11. Add the following using statement to the file:

    C#

    using Todo.Misc;

  12. The OnInvoke method checks which background update are enabled by user through the application’s settings and responds accordingly. Add the following code to the OnInvoke method:

    C#

    protected override void OnInvoke(ScheduledTask task)
    FakePre-8f43c81f6f3445b5906087b3208c6b3d-bbd207a46a7440e0bf9d7a377e06e75fFakePre-ad396ccb8c584e96baa0fa418647f791-2dec8e23421c4a7ba0f5b5893fbb018aFakePre-a02cca1c43a2406f8cc9e56486d5e1d7-d55cb6742ee84658aea747d0ba395490FakePre-84c1137846524e978c54a06d697354f0-f3eda12e680345a1a8de820a624e9be2FakePre-d8eabb880eb74d7b8c1c80b26f716919-4a8fe9aafabd47ffa0e90475ee761d5cFakePre-31d2b380c6334f0a91267bd9a3191f7d-347a4e0608434451ba61a36ac843f9dcFakePre-2995c5c4a7c94d3595208118e6fe0718-ad60e6422c5d4d7e9411010f691dce04FakePre-30ebe4202e554d14b8a3f62f8df8ab60-dac2228272d048ba9162f8370cb8f533FakePre-c4fcb86013f444549cceb15efc9e2fdd-37b8cf02bd7944fabf12ee538e0d3834FakePre-8f34dc7f23ca4c34b72ac2a7c4ad9c6f-f694825ba6ba42ec9486273a8af16a8bFakePre-e0000e7007984366a419a94112522ace-9a9586d414954165b4b5fc8d3d263505FakePre-59b75e95b9d84e34ac9d0fc6b541ebec-76b30b86c5234a9eb32c1da9352e9bffFakePre-0ffdd3dc938a4d02a4fd4641b9449311-1b728b6ab1f0418c9eb6d492500745c3FakePre-50bf464d306c4fe992189586750a1730-4a74301c8c4f4fcaa4446070bb89fde3

    This code block loads the settings and if tiles updates enabled it starts tile update notification procedure.

  13. Add the DoTileUpdates method to the class:

    C#

    void DoTileUpdates(object ununsed) { TaskProgressTileUpdater updater = new TaskProgressTileUpdater(); updater.Start(); }

    This method uses TaskProgressTileUpdater class, which we add later on.

  14. Add TaskProgressTileUpdater.cs and IBackgroundTaskHelper.cs to the TaskLocationAgent project. Both files can be located in the lab installation folder under the Sources\Assets folder.
  15. The IBackgroundTaskHelper.cs file defines an interface that supports creating multiple background task helper classes and running them from a background task agent. TaskProgressTileUpdater implements this interface. Observe its implementation of the DoWork method (partial code):

    C#

    var tiles = ShellTile.ActiveTiles; foreach (ShellTile tile in tiles) { StandardTileData updatedData = new StandardTileData(); Project project = GetProject(tile); if (project != null) { int count = GetOverdueCount(project); string color = GetColorName ( project.Color ); if (count > 0) { updatedData.BackgroundImage = new Uri(string.Format("/Images/Tiles/{0}{1}.png", color, count), UriKind.Relative); } else { updatedData.BackgroundImage = new Uri(string.Format("/Images/Tiles/{0}check.png", color), UriKind.Relative); } updatedData.BackBackgroundImage= new Uri( string.Format("/Images/Tiles/{0}.png", project.Color.Substring(1)), UriKind.Relative); updatedData.BackContent = GetTasks(project); tile.Update(updatedData); } }

    This code snippet iterates over all pinned application tiles, calculates the number of overdue items in the tile’s corresponding project and gets the project’s color. The snippet then updates the tile with an image generated from the gathered data. The tile’s back side is updated as well.

    Note:
    A tile can use two images, titles and contents one for each of the tile’s sides. If backside properties are set, the tile will flip randomly to present the data on its other side.

    The helper methods used in the above snippet (GetProject, GetOverdureCount, GetColorName, etc.) generate random data. In real world application this data could and should come from the application’s SQL CE database.

    Note:
    In the Beta version of Windows Phone Mango tools, the Scheduled Task Agent is limited to 5Mb of memory usage due to a known issue. Therefore, this lab cannot use the application’s database to get actual project data as this will cause the Task Agent to use more than 5Mb of memory, terminating the agent. This issue will be resolved with the next version of Windows Phone Mango tools.

  16. Compile and run the application. Navigate into setting screen and check the “Show Overdue Tasks..” checkbox as shown in figure below:

    Figure 9

    Enabling the background agent

  17. Click the “Apply” button. Now you can navigate away from the application. Once the background agent works your main screen tiles will be updated:

    Figure 10

    Updated project tiles

  18. You can control the background services executed by applications via the phone’s settings/applications/background service screen:

    Figure 11

    Phone background tasks settings screen

  19. Clicking on Tidy will enable you to turn the background services for this application on or off:

    Figure 12

    Application’s background tasks settings

  20. This concludes the task and the lab.