Hands-OnLab
AddingMultitaskingtoYourApplication
Labversion:1.0.0
Lastupdated:11/7/2018
Contents
Overview
Exercise
Task 1 – Pinning Project Tiles to the Start Area
Task 2 – Implementing a Background Agent
Summary
Overview
TheoriginalWindows®Phonedevelopertoolsdidnotallowyourapplicationstoperformoperationswhileinactive.Thislimitedtherangeofexperiencesyoucoulddeliverinyourapplication.WindowsPhoneCodenamed Mangoallowsyourapplicationtoperformoperationsevenwhileinactivebyusingbackgroundagents.Backgroundagentsarecreatedbyaddinganewtypeofprojecttoyourapplication’ssolutioninordertocontaintheagent’slogic.Yourapplicationcanthenregisterthebackgroundagentwiththeoperatingsystemandhaveitscheduletheagenttorunwhileyourapplicationisdormant.ThiseffectivelyallowsformultitaskinginapplicationsyoudevelopusingWindowsPhoneCodenamed Mango.Inaddition,WindowsPhoneCodenamed Mangoallowsanapplicationtopinmultipletilesassociatedwithit,allofwhichleadtodifferentlocationsinsidetheapplicationwhentapped.
Thislabusesthe“Tidy”applicationtodemonstratethesenewfeaturesbygoingthroughthenecessarystepsforimplementingandregisteringabackgroundagentonbehalfofyourapplication.Wewilluseabackgroundagentinordertoupdatetheapplication’stileswhileitisnotactive.
Objectives
Thislabprovidesinstructionstohelpyouachievethefollowing:
- Understandhowtopinmultipleapplicationtilestothestartarea
- Understandhowtomanageanapplication’spinnedtiles
- Implementabackgroundagentinyourapplication
Prerequisites
Thefollowingprerequisiteswillensurethatyougainmostyoucanfromthishands-onlab:
- MicrosoftVisualStudio2010orMicrosoftVisualC#Express2010,andtheWindows®PhoneDeveloperToolsavailableat
- KnowledgeonhowtocreateapplicationsforWindowsPhone7
LabStructure
Thislabincludescontainsasingleexercisewiththefollowingtasks:
- Pinningprojecttilestothestartareaandmanagingaproject’spinnedtiles
- Creatinganewbackgroundagentproject,addingtheagent’slogicandexposingthebackgroundagentthroughyourapplication
Estimatedcompletiontime
Completingthislabshouldtakebetween30and50minutes.
Exercise
Inthisexercise,weshowhowtoaddsecondarytilesrepresentingspecificprojectstothemainscreen.Wethencreateabackgroundagenttoupdatethepinnedprojecttiles.
Task1–PinningProjectTilestotheStartArea
- OpenthestartersolutionfoundlocatedinthelabinstallationfolderunderSource\Begin.
- LocatetheTodo.BusinessprojectandcreateanewclassnamedShellTileHelpersCoreundertheShellprojectfolder.Maketheclassstatic:
C#
publicstaticclassShellTileHelpersCore
{
//…
}
- Addthefollowingusingstatementstothenewlycreatedclassfile:
C#
usingMicrosoft.Phone.Shell;
usingSystem.Linq;
- Thisclasshelpsustoencapsulatetilepinningandunpinningfunctionality. CreateaPinmethodthatwillallowustopintilestothedevice’sstartarea:
C#
publicstaticvoidPin(Uriuri,ShellTileDatainitialData)
{
//Createthetileandpintostart.Thiswillcausetheapptobe
//deactivated
ShellTile.Create(uri,initialData);
}
ShellTileisaclassfromtheMicrosoft.Phone.Shellnamespace,whichisresponsibleformanagingprimaryandsecondarytilesfortheapplication.Theclassprovidesanumberofstaticmethods,whichhelpstocreate/removetilesandaccesstoacollectioncontainingalloftheapplication’stiles,whichcanbeusedtolookforspecifictile.EachapplicationtilehastospecifythenavigationURItofollowwhentheusertapsthetileonthemainscreenaswellasadditionaldataintheformofaShellTileDatainstance,whichdefinesthetile’slook.
Note:WeexaminetheShellTileDataclassanditspropertieslaterinthistask.
- AddtwooverloadsforanUnPinmethodusingthefollowingcodesnippet:
C#
publicstaticvoidUnPin(stringid)
{
varitem=ShellTile.ActiveTiles.FirstOrDefault
(x=>x.NavigationUri.ToString().Contains(id));
if(item!=null)
item.Delete();
}
publicstaticvoidUnPin(Uriuri )
{
varitem=ShellTile.ActiveTiles.FirstOrDefault
(x=>x.NavigationUri==uri);
if(item!=null)
item.Delete();
}
Toremoveapinnedtileweneedtolocateitfirst,andthenexecuteDeletefunctionoffoundtile.BothmethodsusetheShellTile.ActiveTilespropertytotrytolocatethespecifiedtile.Ifthetileislocated,wedeleteit.
- Addtwoadditionalmethodstotheclass.ThesewillbesimilarinnaturetotheUnPinmethodbutwillbeusedtocheckwhetheracertainprojecthasatileinplaceornot:
C#
publicstaticboolIsPinned(Uriuri)
{
varitem=ShellTile.ActiveTiles.FirstOrDefault
(x=>x.NavigationUri==uri);
returnitem!=null;
}
publicstaticboolIsPinned(stringuniqueId)
{
varitem=ShellTile.ActiveTiles.FirstOrDefault
(x=>x.NavigationUri.ToString().Contains(uniqueId));
returnitem!=null;
}
- SavethefileandnavigatetotheTodoproject.LocatetheprojectfoldernamedPushandaddanewclassnamedShellTileHelpersUIunderit.MaketheclassstaticandmakesureitisapartoftheTodonamespace(bydefaultitiscreatedundertheTodo.Pushnamespace):
C#
namespaceTodo
{
publicstaticclassShellTileHelpersUI
{
//…
}
}
ThisclasswillusefunctionalityweintroducedinShellTileHelpersCoretohelpmanagePin/UnpinfunctionalitythroughtheUI.
- Ouraimistocreatetilesthatrepresentasingleprojecteachandprovideabitofinformationabouttheprojectataglance.Asmentionedbefore,eachtileshouldhaveaURIleadingtoapagedisplayingitsassociatedproject.AddthefollowingextensionmethodtoeasilycreateaURIfromagivenproject:
C#
publicstaticUriMakePinnedProjectUri(thisProjectp)
{
returnUIConstants.MakePinnedProjectUri(p);
}
Note:ToseetheimplementationforMakePinnedProjectUri,navigatetotheUIConstants.csfileundertheMiscfolder.
- AddanadditionalmethodtocreateaURIleadingtoatilebackgroundimagethatmatchesaspecifiedproject’scolor.
C#
publicstaticUriGetDefaultTileUri(thisProjectproject)
{
stringcolor=ApplicationStrings.ColorBlue;//defaulttoblue
ColorEntryListlist=App.Current.Resources[UIConstants.ColorEntries]as
ColorEntryList;
if(list!=null)
{
ColorEntryprojectEntry=list.FirstOrDefault(x=>x.Color==
project.Color);
if(projectEntry!=null)
color=projectEntry.Name;
}
returnUIConstants.MakeDefaultTileUri(color);
}
- Addamethodtopinaprojecttothestartarea.Forthis,werequireaShellTileDatavalueandastheclassisabstract,wewillusetheStandardTileDataclassthatderivesfromit.CreatethePinProjectmethodaccordingtothefollowingcode:
C#
publicstaticvoidPinProject(Projectp)
{
//Createtheobjecttoholdthepropertiesforthetile
StandardTileDatainitialData=newStandardTileData
{
//Definethetile’stitleandbackgroundimage
BackgroundImage=p.GetDefaultTileUri(),
Title=p.Name
};
Uriuri=p.MakePinnedProjectUri();
ShellTileHelpersCore.Pin(uri,initialData);
}
- TheUnPinProjectmethodwillsimplypassaproject’sIDtotheUnPinmethodwevreatedpreviously:
C#
publicstaticvoidUnPinProject(Projectp)
{
ShellTileHelpersCore.UnPin(p.Id.ToString());
}
- Similarly,IsPinnedwillrelyonpreviousShellTileHelpersCoreimplementations:
C#
publicstaticboolIsPinned(thisPhoneApplicationPagepage)
{
returnShellTileHelpersCore.IsPinned(
page.NavigationService.CurrentSource);
}
publicstaticboolIsPinned(thisProjectproject)
{
Uriuri=project.MakePinnedProjectUri();
returnShellTileHelpersCore.IsPinned(project.Id.ToString());
}
- SavetheclassandnavigatetotheProjectDetailsView.xaml.csfileundertheViews\Projectsubfolder.ThisclassalreadyhasamethodnamedappBar_OnPinProject.Thismethodisaneventhandlermethod,executedwhentheusertapstheapplicationbarpin/unpinicon.Addthefollowingcodetothemethod’sbody:
C#
privatevoidappBar_OnPinProject(objectsender,EventArgse)
{
Projectproject=DataContextasProject;
if(project.IsPinned())
ShellTileHelpersUI.UnPinProject(project);
else
ShellTileHelpersUI.PinProject(project);
UpdateProjectPinIcons();
}
Thismethodeitherpinorunpinstheproject,dependingonitscurrentstate,andupdatestheapplicationbariconaccordingly.
- LocatethemethodnamedUpdateProjectPinIcons,whichisalreadypresentinthefile.Themethodiscurrentlyempty.Addthefollowingcodesnippettoinitializetheapplicationbariconandtextaccordingtotheproject’spinnedstatus:
C#
privatevoidUpdateProjectPinIcons()
{
if((DataContextasProject).IsPinned())
{
((ApplicationBarIconButton)ApplicationBar.Buttons[(int)Utils.ProjectDetailsViewAppBarButtons.PinProject]).Text=ApplicationStrings.appBar_UnPin;
((ApplicationBarIconButton)ApplicationBar.Buttons[(int)Utils.ProjectDetailsViewAppBarButtons.PinProject]).IconUri=newUri("/Images/appbar.unpin.png",UriKind.Relative);
}
else
{
((ApplicationBarIconButton)ApplicationBar.Buttons[(int)Utils.ProjectDetailsViewAppBarButtons.PinProject]).Text=ApplicationStrings.appBar_Pin;
((ApplicationBarIconButton)ApplicationBar.Buttons[(int)Utils.ProjectDetailsViewAppBarButtons.PinProject]).IconUri=newUri("/Images/appbar.pin.png",UriKind.Relative);
}
}
Thiscodeusestheapplication’sconstantstoretrievelocalizedtextaccordingtotheprojectpinstate.
- Locate the InitializePage method and add the highlighted code in the snippet below to the end of the method:
C#
private void InitializePage()
{
if (!pageInitialized)
{
Guid projectID =
NavigationContext.GetGuidParam(UIConstants.ProjectIdQueryParam);
DataContext = App.ProjectsViewModel.Items.FirstOrDefault(
p => p.Id == projectID);
if ((DataContext as Project).OverdueItemCount > 0)
{
textOverdueCount.Foreground = new SolidColorBrush(Colors.Red);
textOverdueDescription.Foreground = new
SolidColorBrush(Colors.Red);
}
// If we are looking at the default project, disable the deletion
// button
if (projectID == new Guid(Utils.ProjectIDDefault))
{
((ApplicationBarIconButton)ApplicationBar.Buttons[
(int)Utils.ProjectDetailsViewAppBarButtons.DeleteProject]).
IsEnabled = false;
}
UpdateProjectPinIcons();
ApplicationBar.IsVisible = true;
pageInitialized = true;
// Check if this was initialized via deep-link..
if (!NavigationService.CanGoBack)
{
ApplicationBarIconButton homeButton =
new ApplicationBarIconButton {
IconUri = new Uri ("/Images/appbar.home.png",
UriKind.Relative),
IsEnabled = true,
Text= ApplicationStrings.appBar_Home };
homeButton.Click += new EventHandler(homeButton_Click);
ApplicationBar.Buttons.Add ( homeButton ) ;
}
}
}
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.
- Savetheclass,compileandstarttheapplication.Navigatetotheprojectsmanagementscreen(bytappingthe“folder”icononthemainscreenapplicationbar)andcreateatleast2projectswithdifferentcolors.Createafewtasksandassignthemtothedifferentprojects.Yourprojectlistshouldnowlooklikethefollowingscreenshots:
Figure1
Projectsscreen
Taponprojecticontonavigateintotheprojectdetailsscreenandusethe“Pin”icontopintheproject:
Figure2
ProjectDetailsScreen
Figure3
Pinnedproject
- Pressthepinnedtiletoreachtheprojectpagedirectly:
Figure4
Projectdetailsscreen
- Nowthattheprojectispinned,seehowthe“Pin”iconchanged.Taptheunpiniconandnavigateawayfromtheapplication–youwillseethattheprojectwasunpinned:
Figure5
StartScreen
- Itispossibletopin/unpinmultipleprojectsusingtheprevioussteps:
Figure6
Multipleprojecttiles
- Thisconcludesthetask.Inthenexttask,wewilladdanewbackgroundagent,whichwillupdatethepinnedtiles.
Task 2 – Implementing a Background Agent
- Addanewprojecttothesolution.Usethe“WindowsPhoneTaskSchedulerAgent”templateandnameitTaskLocationAgent:
Figure7
Addingnewprojecttothesolution
- AddareferencetothisnewprojectfromtheTodoproject:
Figure8
AddingaReferencetotheBackgroundAgent
- OpentheWMAppManifest.xmlfilefromPropertiesfolderintheTodoprojectandseehowaddingareferencetoanagentprojectcausesthemanifesttoincludeanadditionalelementthatpointstothenewagentweadded:
XML
ExtendedTaskName="BackgroundTask"
BackgroundServiceAgentSpecifier="ScheduledTaskAgent"
Name="TaskLocationAgent"Source="TaskLocationAgent"
Type="TaskLocationAgent.TaskScheduler"/>
</ExtendedTask
- Beforeyouimplementtheagent,addsomecodetostartandstoptheagent.NavigatetotheSettingsViewModel.csfileundertheViewModelsfolder. CreatethefollowingclassmembersintheSettingsViewModelclass:
C#
PeriodicTaskperiodicTask=null;
conststringPeriodicTaskName="TidyPeriodic";
PeriodicTaskisaclassfromtheMicrosoft.Phone.Schedulernamespace,whichrepresentsascheduledtaskthatrunsregularlyforasmallamountoftime.Wewillusethesevariableslaterinthistask.
- LocatetheOnSavemethodandaddthefollowingcodebeforethelinethatcontains“SaveSettings();”:
C#
voidOnSave(objectparam)
{
if(UseBackgroundTaskUpdates||UseBackgroundLocation)
{
EnableTask(PeriodicTaskName,
ApplicationStrings.PeriodicTaskDescription);
}
else
DisableTask(PeriodicTaskName);
SaveSettings();
}
Thismethodwillusetwohelpermethods,whichwecreateinthenextsteps.
- AddtheEnableTaskmethod:
C#
voidEnableTask(stringtaskName,stringdescription)
{
PeriodicTaskt=this.periodicTask;
boolfound=(t!=null);
if(!found)
{
t=newPeriodicTask(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));
}
}
Thismethodtriestolocateapreviouslycreatedperiodictaskandupdatesitsdescriptionandexpirationtime. Otherwise,abrandnewperiodictaskiscreated.Whether a new task is createdor not, a call is placed to launch the agent for debugging purposes if a debugger is attached. TheScheduledActionServiceclassenablesthemanagementofscheduledactions.
Note:Thetask“update”isactuallydonebedeletingtheoldtaskandaddinganewoneinitsstead.
- AddtheDisableTaskmethodtodisableapreviouslyenabledperiodictask:
C#
voidDisableTask(stringtaskName)
{
try
{
PeriodicTaskt;
if(periodicTask!=nullperiodicTask.Name!=taskName)
t=periodicTask;
else
t=periodicTask=ScheduledActionService.Find(taskName)
asPeriodicTask;
if(t!=null)
ScheduledActionService.Remove(t.Name);
}
finally{};
}
Likeinthepreviousstep,thiscodelooksforanexistingtaskandremovesitusingtheSchduledActionServiceclass.
- ModifytheSettingsViewModel’sconstructortopickuptheperiodictask’sstatusatinitialization–modifytheconstructoraccordingtothefollowingcodesnippet:
C#
publicSettingsViewModel()
{
syncProvider=newLocalhostSync();
syncProvider.DownloadFinished+=DownloadFinished;
syncProvider.UploadFinished+=UploadFinished;
syncProvider.DownloadUploadProgress+=OperationProgress;
periodicTask=ScheduledActionService.Find(PeriodicTaskName)
asPeriodicTask;
if(periodicTask!=null)
IsBackgroundProcessingAllowed=periodicTask.IsEnabled;
else
IsBackgroundProcessingAllowed=true;
LoadSettings();
}
- SavethisclassandreturntotheTaskLocationAgentproject.AddareferencetotheTodo.BusinessprojectintheTaskLocationAgentproject.
- NavigatetotheTaskScheduler.csfile.Letusreviewthisfile.Ithastwomethods: OnInvoke,calledwhenaperiodictaskisinvokedbythescheduledactionservice,andOnCancel,calledwhenanagentrequestiscanceled.WewillleavetheOnCanelimplementationasis.
Note:Thislabwillnotfocusonlocationupdates,whicharepartofthefullapplicationandareperformedusingthisbackgroundagent.Theendsolutionforthislabdoescontaintherelevantcode,butwewillnotcoveritaspartofthelab.
- Addthefollowingusingstatementtothefile:
C#
usingTodo.Misc;
- TheOnInvokemethodcheckswhichbackgroundupdateareenabledbyuserthroughtheapplication’ssettingsandrespondsaccordingly.AddthefollowingcodetotheOnInvokemethod:
C#
protectedoverridevoidOnInvoke(ScheduledTasktask)
{
SettingsWorkaroundpreferences=SettingsWorkaround.Load();
if(preferences==null)
{
NotifyComplete();
return;
}
if(preferences.UseTileUpdater)
DoTileUpdates(null);
this.NotifyComplete();
}
Thiscodeblockloadsthesettingsandiftilesupdatesenableditstartstileupdatenotificationprocedure.
- AddtheDoTileUpdatesmethodtotheclass:
C#
voidDoTileUpdates(objectununsed)
{
TaskProgressTileUpdaterupdater=newTaskProgressTileUpdater();
updater.Start();
}
ThismethodusesTaskProgressTileUpdaterclass,whichweaddlateron.
- AddTaskProgressTileUpdater.csandIBackgroundTaskHelper.cstotheTaskLocationAgentproject.BothfilescanbelocatedinthelabinstallationfolderundertheSources\Assetsfolder.
- TheIBackgroundTaskHelper.csfiledefinesaninterfacethatsupportscreatingmultiplebackgroundtaskhelperclassesandrunningthemfromabackgroundtaskagent.TaskProgressTileUpdaterimplementsthisinterface.ObserveitsimplementationoftheDoWorkmethod(partialcode):
C#
vartiles=ShellTile.ActiveTiles;
foreach(ShellTiletileintiles)
{
StandardTileDataupdatedData=newStandardTileData();
Projectproject=GetProject(tile);
if(project!=null)
{
intcount=GetOverdueCount(project);
stringcolor=GetColorName(project.Color);
if(count0)
{
updatedData.BackgroundImage=
newUri(string.Format("/Images/Tiles/{0}{1}.png",
color,count),UriKind.Relative);
}
else
{
updatedData.BackgroundImage=
newUri(string.Format("/Images/Tiles/{0}check.png",
color),UriKind.Relative);
}
updatedData.BackBackgroundImage=newUri(
string.Format("/Images/Tiles/{0}.png",
project.Color.Substring(1)),UriKind.Relative);
updatedData.BackContent=GetTasks(project);
tile.Update(updatedData);
}
}
Thiscodesnippetiteratesoverallpinnedapplicationtiles,calculatesthenumberofoverdueitemsinthetile’scorrespondingprojectandgetstheproject’scolor.Thesnippetthenupdatesthetilewithanimagegeneratedfromthegathereddata.Thetile’sbacksideisupdatedaswell.
Note:Atilecanusetwoimages,titlesandcontentsoneforeachofthetile’ssides.Ifbacksidepropertiesareset,thetilewillfliprandomlytopresentthedataonitsotherside.
Thehelpermethodsusedintheabovesnippet(GetProject,GetOverdureCount,GetColorName,etc.)generaterandomdata.Inrealworldapplicationthisdatacouldandshouldcomefromtheapplication’sSQLCEdatabase.
Note:IntheBetaversionofWindowsPhoneMangotools,theScheduledTaskAgentislimitedto5Mbofmemoryusageduetoaknownissue.Therefore,thislabcannotusetheapplication’sdatabasetogetactualprojectdataasthiswillcausetotheTaskAgenttousemorethan5Mbofmemory,terminatingtheagent.ThisissuewillberesolvedwiththenextversionofWindowsPhoneMangotools.
- Compileandruntheapplication.Navigateintosettingscreenandcheckthe“ShowOverdueTasks..”checkbox asshowninfigurebelow:
Figure9
Enablingthebackgroundagent
- Clickthesavebutton.Nowyoucannavigateawayfromtheapplication.Oncethebackgroundagentworksyourmainscreentileswillbeupdated:
Figure10
Updatedprojecttiles
- Youcancontrolthebackgroundservicesexecutedbyapplicationsviathephone’ssettings/applications/backgroundservicescreen:
Figure11
Phonebackgroundtaskssettingsscreen
ClickingonTidywillenableyoutoturnthebackgroundservicesforthisapplicationonoroff:
Figure12
Application’sbackgroundtaskssettings
- Thisconcludesthetaskandthelab.
Summary
Thislabhastakenyouthroughthenecessarystepsforcreatingabackgroundagenttoupdatetheapplication’stiles.Usingthisisanexample,youshouldnowhaveagreaterunderstandingofWindowsPhoneMango’snewmultitaskingcapabilitiesandshouldknowhowtoincorporatethesecapabilitiesintoyourfutureapplications.
Page | 1
©2010 Microsoft Corporation. All rights reserved.