动手实验
向你的应用添加多任务
实验版本: 1.0.0
最后更新: 2/29/2012
目录
练习1 – 把应用程序的 Tiles 设置到开始菜单项中... 4
原始的Windows®Phone 开发工具不允许你的应用程序在未运行的时候执行操作。这个限制了你在你的应用程序中的发挥。但是代号为 Mango的Windows Phone 允许你的应用程序在未激活时通过使用后台代理来执行操作。为了包含后台代理的逻辑你可以在你的应用程序解决方案中添加一个新类别的项目。这样你的应用程序就在操作系统中注册后台代理并且在你的程序休眠的时候安排代理运行。这可以有效的使你开发的应用程序在Windows Phone Mango中使用多任务。另外,代号Mango的WindowsPhone允许一个应用程序用多个tile与之关联,通过选择开始菜单上的这些tiles可以使你很快定位到应用程序的不同位置。
本次实验通过这个名叫“Tidy”的应用来展示这些新特性,需要必要的步骤为你的应用实施和注册后台代理。我们将使用一个后台代理来更新处于休眠状态程序的tiles。
目标
这个实验提供下面的指导帮你完成任务:
· 了解怎样把多任务的tiles放到开始菜单中
· 了解如何管理一个应用的tiles
· 在你的应用中运行一个后台代理
首要必备
下面这些先决条件将确保你从这次动手实验中得到更多:
· Microsoft Visual Studio 2010 或者 Microsoft Visual C# Express 2010, 和 Windows® Phone DeveloperTools 可以从这个网址获得http://go.microsoft.com/?linkid=9772716
· 知道关于如何创建 Windows Phone 7
实验架构
这个实验在下面的任务中包含一个单独的练习:
1. 把一个应用程序的tiles钉到开始菜单中和在开始菜单中管理一个应用程序的tiles
2. 创建一个新的后台代理工程,添加这个代理的逻辑并且通过你的应用程序展示这个后台代理
估算完成所需的时间
完成这个实验将要花费30到50分钟
在这个练习中,我们展示如何在主屏幕上为主程序添加次级tiles。然后我们创建一个后台代理去更新已经订到桌面的tiles。
任务 1 –把工程的tiles钉到开始菜单上
1. 打开位于实验存放文件目录Source\Begin下的解决方案的开始文件。
2. 定位到Todo.Business 工程并且在Shell工程文件夹下新建一个名为ShellTileHelpersCore类。 设置这个类为 static的:
C#
public static class ShellTileHelpersCore
{
// …
}
3. 在这个新建类的文件上面添加下面的命名空间的声明:
C#
using Microsoft.Phone.Shell;
using System.Linq;
4. 这个类帮我们封装了 tile的pinning和 unpinning 函数。创建一个允许我们把tiles 钉到设备开始菜单的方法:
C#
public staticvoid Pin(Uriuri, ShellTileData initialData)
{
// Create the tile and pin to start. This will cause the appto be
// deactivated
ShellTile.Create(uri, initialData);
}
ShellTile是一个定义在 Microsoft.Phone.Shell 命名空间中的类,这个类主要负责管理这个应用的主要和次要的tiles 。这个类提供一系列的用来 创建/移除 tiles的静态方法,和一个包含这个应用程序所有tiles的集合,并且可以从这个集合中查找特定的tile。每一个应用程序的tile必须指定特定的URI,使得用户通过点击开始菜单上的tile或者通过ShellTileData 声明的对应这个 tile的对象实例来跟踪。
注释:在这个任务的后面我们检测这个ShellTileData类和这个类的属性。
5. 使用下面代码片段添加两个重载的UnPin方法:
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 ();
}
为了删除已经钉在开始菜单上的tile,我们首先定位到这个tile,并且只执行这个指定tile对象的 Delete方法。这两个方法都是通过ShellTile.ActiveTiles的属性尝试定位到特定的tile。如果定位到这个tile,就删除它
6. 添加两个额外的方法到这个类中。这两个方法对于UnPin方法本质上是相似的,但它们用来检测某个工程是否包含特定的tile:
C#
public staticbool IsPinned(Uriuri)
{
var item = ShellTile.ActiveTiles.FirstOrDefault
(x => x.NavigationUri== uri);
return item != null;
}
public staticbool IsPinned( stringuniqueId )
{
var item = ShellTile.ActiveTiles.FirstOrDefault
(x => x.NavigationUri.ToString().Contains(uniqueId));
return item != null;
}
7. 保存这个文件并且导航到Todo工程。定位到名叫Push的工程文件夹下,在这个文件下添加一个名叫ShellTileHelpersUI的类。设置这个类为static的并且确保它是Todo命名空间下的(由于疏忽它是在Todo.Push命名空间下创建的):
C#
namespace Todo
{
public static class ShellTileHelpersUI
{
//…
}
}
这个类将使用从中ShellTileHelpersCore中引进的功能去帮助管理表现在UI上的Pin/Unpin功能。
8. 我们的目的是创建tiles,这些tiles代表每个单独的工程并且在首页提供关于这个工程的简单的信息。就像前面提到的,每个tile应该包含一个URI属性来指向一个显示这个工程的页面。添加下面的的扩展的方法用来轻松新建来自一个指定工程的URI:
C#
public staticUri MakePinnedProjectUri(this Project p)
{
return UIConstants.MakePinnedProjectUri(p);
}
注释:导航到Misc文件夹下的UIConstants.cs类文件去看MakePinnedProjectUri方法的实现。
9. 添加一个额外的方法用来指向一个tile的匹配相应工程颜色的背景图片的URI。
C#
public staticUri 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);
}
10. 添加一个把应用程序钉到开始菜单上的方法。因此,我们需要使用ShellTileData,但是因为这个类是抽象的,我们将用一个StandardTileData类去继承它。依照下面的代码创建一个PinProject方法:
C#
public staticvoid PinProject (Projectp)
{
// 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);
}
11. 这个UnPinProject方法就是简单的传递工程的 ID 到我们之间创建的UnPin方法
C#
public staticvoid UnPinProject(Projectp)
{
ShellTileHelpersCore.UnPin(p.Id.ToString() );
}
12. 相似的,IsPinned方法是依赖前面实现的ShellTileHelpersCore方法:
C#
public staticbool IsPinned ( thisPhoneApplicationPage page )
{
return ShellTileHelpersCore.IsPinned(
page.NavigationService.CurrentSource);
}
public staticbool IsPinned(thisProject project)
{
Uri uri = project.MakePinnedProjectUri();
return ShellTileHelpersCore.IsPinned(project.Id.ToString() );
}
13. 保存这个类并且导航到在Views\Project路径下的ProjectDetailsView.xaml.cs文件。这个类已经包含了一个名叫appBar_OnPinProject的方法。这个方法是一个事件委托的方法,当用户点击applicationbar菜单上的pin/unpin图标时执行。把下面的代码添加到方法体中:
C#
private voidappBar_OnPinProject(object sender, EventArgs e)
{
Project project = DataContext as Project;
if(project.IsPinned() )
ShellTileHelpersUI.UnPinProject(project);
else
ShellTileHelpersUI.PinProject(project );
UpdateProjectPinIcons();
}
这个方法根据当前的状态pin或者unpin当前的工程到开始菜单,并且和application bar icon是一致的。
14. 定位到文件中已经存在的名叫UpdateProjectPinIcons的方法。当前的方法是空的。添加下面代码片段中的代码,这是根据工程钉到开始菜单上的状态,用来初始化application bar icon和对应的文本:
C#
private voidUpdateProjectPinIcons()
{
if ((DataContext as Project).IsPinned())
{
((ApplicationBarIconButton)ApplicationBar.Buttons[(int)Utils.ProjectDetailsViewAppBarButtons.PinProject]).Text= ApplicationStrings.appBar_UnPin;
((ApplicationBarIconButton)ApplicationBar.Buttons[(int)Utils.ProjectDetailsViewAppBarButtons.PinProject]).IconUri= new Uri("/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= new Uri("/Images/appbar.pin.png", UriKind.Relative);
}
}
这段代码依照当前工程pin的状态来重新获取对应的文本。
15. 定位到InitializePage方法,并且把下面高亮的代码片段添加到这个方法的末尾:
C#
private voidInitializePage()
{
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 = newSolidColorBrush(Colors.Red);
textOverdueDescription.Foreground =new
SolidColorBrush(Colors.Red);
}
// If we are looking at the defaultproject, disable the deletion
// button
if (projectID == newGuid(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 =
newApplicationBarIconButton {
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 ) ;
}
}
}
这个新代码块托管一个特殊的情况,是当应用程序是通过按钉在开始菜单上的tiles启动时的情况。当通过tiles加载应用时,用户将会看到的是关联的项目的详情。由于这个项目的详情页是第一页,当按下手机的back键时,程序将直接关闭而不是返回到主菜单。因为这个原因,当通过开始菜单的tile加载应用时,我们将在application bar中添加一个特殊的按钮来使用户可以直接返回应用程序的主菜单。
16. 保存这个类,编译并运行程序。导航到工程管理页面(通过点击主屏幕的application bar 上的“folder”图标)并且生成至少两个不同颜色的工程。运行一些任务并指派他们到不同的工程。你的工程列表现在应该看起来像下面的截图:
图形 1
程序屏幕
点击程序图标,导航到程序详述页,并且通过“Pin”图片把工程钉到开始菜单:
图形2
工程详述页
图形3
钉到开始菜单的图标
17. 点击这个tile可以直接到达应用程序页:
图形4
工程详述页
18. 既然工程被钉到开始菜单了,观察“Pin”图标是怎么改变的。点击unpin图标然后从这个程序导航走---然后你将看到工程的tile已经不在开始菜单了:
图形5
StartScreen
19. 通过前面的步骤同样可以添加或删除其他应用程序在开始菜单上的tile:
图形6
更多应用程序的 tiles
20. 这里总结了这个任务。在下一个任务,我们添加一个后台代理,用这个代理更改程序的tile。
任务 2 – 实现一个后台代理
1. 在这个解决方案中添加一个新项目工程。我们使用“Windows Phone TaskScheduler Agent”模板,给它取名为TaskLocationAgent:
图形7
Adding new project to the solution
2. 在这个新项目中添加对Todo项目的引用:
Figure 8
Adding a Reference to the Background Agent
打开位于Todo工程项目下Properties文件夹下的WMAppManifest.xml文件,添加引用到一个代理工程中,manifest文件需要包括一个指向新代理的元素。
XML
<ExtendedTask Name="BackgroundTask">
<BackgroundServiceAgentSpecifier="ScheduledTaskAgent"
Name="TaskLocationAgent" Source="TaskLocationAgent"
Type="TaskLocationAgent.TaskScheduler" />
</ExtendedTask>
3. 在使这个代理生效前,添加一些代码用来开始和停止这个代理。导航到ViewModels文件夹下的SettingsViewModel.cs文件。在SettingsViewModel类中新建下面类的成员
C#
PeriodicTask periodicTask = null;
const string PeriodicTaskName = "TidyPeriodic";
PeriodicTask是Microsoft.Phone.Scheduler命名空间下的类,它用来代表那些有规律运行一小段时间的预定义好的任务。后面我们将在这个任务中使用这些变量。
4. 定位到OnSave方法中,并且在“SaveSettings();”方法前面添加下面高亮显示的代码:
C#
void OnSave( object param )
{
if (UseBackgroundTaskUpdates || UseBackgroundLocation)
{
EnableTask(PeriodicTaskName,
ApplicationStrings.PeriodicTaskDescription);
}
else
DisableTask(PeriodicTaskName);
SaveSettings();
}
这个方法将要使用两个提供辅助的方法,我们将在下面的步骤中创建。
5. 添加EnableTask方法:
C#
void EnableTask(string taskName,string description)
{
PeriodicTaskt = this.periodicTask;
bool found =(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));
}
}
这个方法尝试定位到之前创建的一个定期的任务,并且更新它的描述和有效时间。另外,一个新的周期性任务被创建。无论新任务创建成功与否,如果调试器被加入,一个调用将会被放置在调试器代理中。ScheduledActionService类激活计划动作的管理。
注释:在删除旧的任务和添加一个新的任务时这个“update”任务才被真正的调用。
6. 添加一个DisableTask方法用来使之前添加的一个周期执行的方法失效:
C#
void DisableTask(string taskName)
{
try
{
PeriodicTaskt;
if (periodicTask!= null && periodicTask.Name != taskName)
t = periodicTask;
else
t = periodicTask = ScheduledActionService.Find(taskName)
as PeriodicTask;
if (t !=null)
ScheduledActionService.Remove(t.Name);
}
finally { };
}
就像前面的步骤,这些代码寻找一个现存的任务并且使用SchduledActionService类去移除它
7. 更改SettingsViewModel’s的构造函数用来在初始化时获得周期性任务的状态----依照下面的代码段更改构造函数:
C#
public SettingsViewModel()
{
syncProvider = newLocalhostSync();
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();
}
8. 保存这个类,然后返回到TaskLocationAgent项目工程。在TaskLocationAgent工程下对Todo.Business工程添加一个引用。
9. 导航到TaskScheduler.cs文件。让我们复查这两个方法:OnInvoke方法,当预订的行为服务执行周期性任务时调用这个方法,和OnCanel方法,当一个代理请求被取消时调用。我们将要保持OnCanel方法之前的实现。
注释:这个实验将不会把焦点放在位置更新上,它们是整个应用的一部分并且是使用后台代理调用的。该实验的最终解决方案包含相关的代码,但是我们不会作为实验的一部分去覆盖它。
10. 在这个文件中添加下面的命名空间:
C#
using Todo.Misc;
11. OnInvoke方法,用来检查由用户通过对应用程序的设置实现背景更新并相应的做出反应。在OnInvoke方法中添加下面代码:
C#
protected override void OnInvoke(ScheduledTask task)
{
SettingsWorkaroundpreferences = SettingsWorkaround.Load();
if (preferences == null)
{
NotifyComplete();
return;
}
if ( preferences.UseTileUpdater )
DoTileUpdates(null);
this.NotifyComplete();
}
这个代码块加载设置,并且如果tiles更新时允许它开始tile更新通知进程。
12. 在这个类中添加DoTileUpdates方法:
C#
void DoTileUpdates(object ununsed)
{
TaskProgressTileUpdaterupdater = new TaskProgressTileUpdater();
updater.Start();
}
这个方法使用到了我们后面添加的TaskProgressTileUpdater类。
13. 添加TaskProgressTileUpdater.cs和IBackgroundTaskHelper.cs文件到TaskLocationAgent工程中。这两个文件都可以在实验安装目录Sources\Assets文件夹下找到。
14. 在IBackgroundTaskHelper.cs文件中定义一个接口,这个接口支持创建多后台任务帮助类并且在后台任务代理中运行它们。TaskProgressTileUpdater实现这个接口。观察这个DoWork方法的实现(部分代码):
C#
var tiles = ShellTile.ActiveTiles;
foreach (ShellTile tile in tiles)
{
StandardTileDataupdatedData = new StandardTileData();
Project project= GetProject(tile);
if (project!= null)
{
int count= GetOverdueCount(project);
string color= GetColorName ( project.Color );
if (count> 0)
{
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= new Uri(
string.Format("/Images/Tiles/{0}.png",
project.Color.Substring(1)),UriKind.Relative);
updatedData.BackContent = GetTasks(project);
tile.Update(updatedData);
}
}
这个代码段迭代所有pinned的工程的tiles,计算工程关联的tile中过期项目的数目并且获的工程的颜色。然后这个代码段用聚集数据产生的一个图片更新这个tile。这个tile的侧面同样被更新了。
注释:每个tile可以用两张图片,标题和tile两侧的内容。如果设置了背面属性,这个tile将随机在它的另一个侧显示数据。
这些辅助的方法在上面的代码段(GetProject, GetOverdureCount, GetColorName,等)中用来产生随机数据。在现实应用中这些数据可以并且应该来自于应用程序的SQL CE数据库。
注释:在Windows Phone Mango Beta版本的tools中,这些预安排的任务被限制最多使用一个已知设备 5Mb的内存容量。因此,在这个实验中不会使用应用程序的数据库来获得实际项目中的数据,因为它会致使任务代理使用超过5Mb的内存从而被终止掉。这个问题将在另一个版本的WindowsPhone Mango tools中得到解决。
15. 编译并且运行这个应用程序。导航到设置页并且查看“Show Overdue Tasks..”复选框,就像下面图片显示的:
图片 9
授权这个后台代理
16. 单击保存按钮。现在你从这个应用程序中退出。一旦后台代理开始工作,你的主菜单的tile就好被更新:
图片 10
更新工程的tiles
17. 你可以在手机的settings/applications/background服务页面控制执行后台服务。
图片 11
手机背景任务设置页
点击 Tidy 你可以使这个应用程序的背景服务开或者关:
图片 12
应用程序背景任务设置页
18. 面是所有的任务和实验。
这个实验已经带着你通过必要的步骤创建后台代理去更新应用程序的tiles。通过这个实验,现在你应该对Windows Phone Mango的多任务的能力有个一个深刻的理解,并且应该知道怎样把这些整合到你未来的应用中去。