動手實驗
向你的應用添加多任務
實驗版本: 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的多任務的能力有個一個深刻的理解,並且應該知道怎樣把這些整合到你未來的應用中去。