Silverlight 實驗室

建置您的第一個 Windows Phone 7 應用程式

 

概觀

本實驗將向您介紹為 Windows Phone 建置和測試一個 Silverlight 應用程式所需要的工具和過程。本實驗透過一個簡單的益智遊戲開發的完整過程以及其不同階段:開啟一個新的專案、添加控制項、程式碼隱藏、測試和除錯,來向您展示開發 Windows Phone 應用程式的基本原則。

這個實驗涵蓋了 Windows Phone 平台幾個關鍵的特性,包括:導覽功能 (Navigation),多點觸控(Multi-touch) 和隔離儲存區 (Isolated Storage)。

實驗目標

這個實驗的對象是對 Microsoft 工具,例如 Visual Studio 瞭解較少,且對 Silverlight 非常陌生的開發人員。如果您對 Silverlight 的程式設計模型很熟悉,且已經開始利用 Visual Studio 和 Microsoft Expression 套件進行工作,那麼您可以考慮略過本實驗。從另一方面講,如果您對 Silverlight 完全不瞭解,我們強烈建議您查閱一些與 Silverlight 相關的實驗來加強您的 Silverlight 技能,這些實驗的內容可以從下面這個網站找到:https://silverlight.net/learn/

在這個實驗手冊中,您將會學到:

  • 對 Windows Phone 開發人員使用的工具更加熟悉:針對 Windows Phone 的 Microsoft Visual Studio 2010 Express 版本和 Windows Phone Emulator。這些就是您建置和測試任何可被管理的 Windows Phone 應用程式所需要的工具。
  • 學習針對 Windows Phone 應用程式的 Silverlight 的基礎結構,同時學到 Silverlight 和針對 Windows Phone 的 Silverlight 之間的不同
  • 利用針對 Windows Phone 的 Microsoft Visual Studio 2010 Express 和 Windows Phone Emulator 編寫、測試、部署和除錯 Windows Phone 的應用程式。

前置條件

要完成本動手實驗,您需要以下工具:

  • 用於 Windows Phone 的 Microsoft Visual Studio 2010 Express 或者是 Microsoft Visual Studio 2010 Windows Phone Developer Tools
  • Windows Phone 開發者工具

**說明:**您可從該網址下載工具 http://developer.windowsphone.com


安裝

為了方便,本實驗手冊中的大部分程式碼以 Visual Studio 程式碼片段 (Code Snippet) 的形式提供。要安裝程式碼片段:

在 lab's 下的 Source \ Setup 資料夾,執行 .vsi 安裝程式。

**說明:**如果你在執行程式碼片段的安裝程式時遇到問題,你可以通過拷貝 Source \ Setup \ CodeSnippets 資料夾下的所有 .snippet 檔到目錄:

\ My Documents \ Visual Studio 2010 \ Code Snippets \ Visual C# \ My Code Snippets 來進行手動安裝程式碼片段


使用程式碼片段

利用這些程式碼片段,您實際上已經獲得了程式設計的操作中所會用到的所有程式碼。這個實驗說明將會準確的告訴您何時使用這些程式碼。例如


圖表 1
使用 Visual Studio 程式碼片段把程式碼插入到你的程式裡

為了把程式碼片段添加到 Visual Studio 中,你只要把游標放在你想插入的程式碼上,開始輸入該段落的名字 (沒有空格和連字號),看到 IntelliSense 顯示的段落名稱,然後當你想要的段落名稱被選中時,按 Tab 鍵兩次,程式碼將會被插入到游標的位置。


圖表 2
開始輸入段落名稱


圖表 3
按 Tab 鍵來選取已選擇的段落


圖表 4
再次按 Tab 鍵來展開段落

使用滑鼠而不是鍵盤來插入程式碼片段:在你想插入程式碼片段的地方按右鍵,選擇 My Code Snippets 下的 **Insert Snippet,**然後從列表中挑選相關的程式碼片段。

想學習更多的 Visual Studio 程式碼片段知識,包括如何建立自己的程式碼片段,請參考 https://msdn.microsoft.com/en-us/library/ms165392.aspx


練習

本實驗手冊包含以下練習:

  1. 利用 Microsoft Visual Studio 2010 Express 為 Windows Phone 建立 Windows Phone 應用程式
  2. 在 Visual Studio 中建立拼圖板
  3. 利用隔離儲存區來保存遊戲狀態

完成此實驗需耗時:60分鐘。


練習 1:利用 Microsoft Visual Studio 2010 Express 為 Windows Phone 建立 Windows Phone 應用程式

在這個練習中,您將 學會如何建立、測試、部署和運行您的第一個 Windows Phone 應用程式。您將會學會如何使用 Windows Phone 開發者工具,包括:用於 Windows Phone IDE 的免費工具 Microsoft Visual Studio 2010 Express 和 Windows Phone Emulator。

本練習的任務包括為 Windows Phone 建立一個新的 Silverlight 應用程式專案,設計應用程式的起始頁面,提供一些基礎的容錯處理。您同樣將會把應用程式運行在工作站 (即開發人員的本機電腦) 的 Windows Phone Emulator 上。

**說明:**本次動手實驗的步驟闡述了 Windows Phone 上 Microsoft Visual Studio 2010 Express 的使用過程,但是它們同樣適用於附加 Windows Phone 開發工具的 Microsoft Visual Studio 2010。在操作指南裡提到的 Visual Studio,對這兩個產品都適用。

任務 1 – 在 Visual Studio 中建立一個 Windows Phone 應用程式專案

這個任務中,您將會利用針對 Windows Phone 的 Microsoft Visual Studio 2010 Express 工具內嵌的範本來建立一個 Windows  Phone 應用程式專案的 Silverlight,可以把這做為您第一個要開發的 Window Phone 應用程式的起點。

1. 從作業系統的 Start | All Programs | Microsoft Visual Studio 2010 Express | Microsoft Visual Studio 2010 Express for Windows Phone 中啟動用於 Windows Phone 應用程式開發的 Microsoft Visual Studio 2010 Express 。

**Visual Studio 2010:**從作業系統的 Start | All Programs | Microsoft Visual Studio 2010 啟動 Visual Studio 2010。

2. 在 File 功能表,選擇 New Project

**Visual Studio 2010:**在 File功能表, 指向 New 然後選擇 Project

3. 在 New Project 對話方塊,從安裝的範本清單裡選擇 Silverlight for Windows Phone 類別, 然後選擇 Windows Phone 應用程式範本。把 name 欄改為 Windows Phone Puzzle,同時把 location 設為在實驗目錄下 Source 資料夾中的 Ex1-CreatingWP7Apps。更改solution name 為 **Begin,**然後點擊 OK


圖表 5
在用於 Windows Phone 的 Microsoft Visual Studio2010 Express 中建立一個新的 Windows Phone 應用程式

**說明:**如果您已經完成培訓教材中 Hello Phone 的實驗,您可能已經對下面的步驟非常熟悉,並會直接建立一個 begin 解決方案執行這個練習中任務 3 的操作。

4. 在 Solution Explorer 中,查看基於 Windows Phone 應用程式範本產生的解決方案的結構。Visual Studio 解決方案是該解決方案下所有專案項目的容器;本例中的解決方案只包含了一個名為 Windows Phone Puzzle 的 Silverlight Windows Phone 專案項目。


圖表 6
Solution Explorer 包含一個名為 Windows Phone Puzzle 的應用程式

**說明:**Solution Explorer 允許用戶在解決方案或專案項目中查看其包含的項目 (Item) 以及對項目進行管理操作。要啟動 Solution Explorer,在鍵盤上按下 CTRL + W,S 或者在 View 功能表,選擇 Other Windows | Solution Explorer

Windows Phone Puzzle項目包含以下檔案 (Item):

檔案 描述
App.xaml / App.xaml.cs 定義應用程式的入口點,初始化應用程式範圍內的資源,,顯示應用程式使用者介面。
MainPage.xaml / MainPage.xaml.cs 定義應用程式中的程式頁面 (帶有使用者介面的頁面)。
ApplicationIcon.png 一種帶有圖示的影像檔,代表了手機應用程式清單中應用程式的圖示。
Background.png 一種帶有圖示的影像檔,代表了在開始頁面上應用程式的圖案。
SplashScreenImage.jpg 這個圖片會在應用程式第一次被啟動時顯示。啟動畫面會給使用者一個即時的回應,告訴使用者應用程式正在啟動,直到成功顯示應用程式的第一個頁面。使用者的啟動畫面可以和應用程式的一個頁面設計的非常相似,這樣能給使用這一個應用程式被快速載入的感覺。
Properties \ AppManifest.xml 一個生成應用程式封裝所必需的應用程式清單檔。
Properties \ AssemblyInfo.cs 包含名稱和版本的中繼資料,這些中繼資料將被嵌入到生成的程式集。
Properties \ WMAppManifest.xml 一個包含與 Windows Phone Silverlight 應用程式相關的特定中繼資料的清單檔,且包含了用於 Windows Phone 的 Silverlight 所具有的特定功能。
References folder 一些參考組件程式庫 (Reference Libraries) 的清單,為應用程式的工作提供功能和服務。

5. 首先,在 Solution Explorer 中按右鍵 App.xaml 並選擇 View Designer。注意這個檔案的 XAML 標記,它有一個叫 Application 的根項目,在這個根項目中有一個叫 Application.Resources 的段落。 在此處,您可以定義應用程式層次 (Application Level) 的資源,例如整個應用程式使用的顏色、畫筆以及樣式物件。

這段 XAML 程式碼初始化了 ApplicationApplicationLifetimeObjects 屬性,建立了一個 PhoneApplicationService 物件。PhoneApplicationService 類別提供了應用程式不同生命階段的瀏覽功能。包含了對應用程式處於空閒狀態的管理,也包含了當應用程式處於有效或無效狀態時的管理。


圖表 7
基於 Windows Phone 應用程式範本產生的預設 App.xaml 文件

說明:App.xaml 檔,連同其程式碼隱藏檔 App.xaml.cs,共同定義了 Application 類別的實例。這個類別封裝了用於 Windows Phone 的 Silverlight,並提供了一個進入點(Application Entry Point)。

6. 現在,在 Solution Explorer 中按右鍵 App.xaml  並選擇 View Code 來打開一個程式碼隱藏檔。請注意,在 Application 的衍生類別的建構函數中已經有一個針對 UnhandledException 事件的控制碼。在後面的實驗中,您將會更新範本生成的控制碼來實現轉移到一個錯誤頁面及顯示錯誤資訊的功能。

Application 類別的 RootFrame 屬性標識了應用程式的啟動頁面。所有的 Windows Phone 應用程式都有一個最頂層的容器元素,它的資料類型是 PhoneApplicationFrame。這個框架承載了一個或多個用來標識應用程式內容的 PhoneApplicationPage 元素,同時它還被用來處理不同頁面之間的導覽切換。


圖表 8
應用程式的程式碼隱藏檔展示了全域事件控制碼

**說明:**以 Windows Phone 應用程式範本為基礎的 Silverlight 所生成 Application 類,除了對上述事件的控制碼外,還包含了對 LaunchingClosing 事件的控制碼。您可以通過更新這些方法的程式碼來實作當 Windows Phone 應用程式啟動和關閉過程中執行自己的自訂程式碼。

7. 新生成的專案項目包含一個預設的檔,該檔裡包含了用來定義應用程式主要使用者介面的 XAML 標記。用戶如果要在 designer 中查看這個檔,則需在 Solution Explorer 中的 MainPage.xaml 上按兩下。

預設狀態下,designer 會把這個檔用分隔畫面顯示。一個視窗顯示 XAML 標記,另一個視窗顯示了所見即所得 (WYSIWYG) 的使用者介面元素設計檢視。範本中顯示的是應用程式的名稱和標題,您可以根據需要刪除它們。除此之外,XAML 檔還可提供給您一個空白的類似畫布的操作區域,您可以通過添加控制項來建立自己的應用程式的使用者介面。


圖表 9
XAML 設計工具 (designer) 展示了應用程式的主要使用者介面

**說明:**可擴展的應用程式標記語言 (XAML) 是一種宣告型語言 (declarative language)。您可以利用宣告 XAML 標記來建立可見的使用者介面元素。然後再利用一個程式碼隱藏檔對 XAML 中宣告的事件做出反應,並操作在 XAML 中已經宣告的物件。用一種基於 XAML 的宣告型語言來建立介面,從原型到生產整個過程都是非常直覺的,特別是對那些有網頁設計和技術背景的人來說更是如此。

8. ApplicationIcon.png 檔包含的圖示檔,就是那些在電話設備上快速啟動頁面中代表應用程式的圖示。您可以在Solution Explorer 上在該圖示檔按兩下,Visual Studio 會使用在你機器上註冊的圖像編輯應用程式來打開它,例如 Paint.exe (小畫家)

**說明:**Visual Studio 2010中,在Solution Explorer 上對該圖示檔按兩下,該檔會在內嵌的圖像編輯器中打開。

9. 一個 Windows Phone 應用程式通常需要用到基礎平台或者其他類別庫來提供服務。要使用這個功能,應用程式需要首先引用實現這些服務的組件 (assemblies)。要顯示一個專案引用的組件,在 Solution Explorer 中展開 References 節點,然後查看組件清單。它包含了 Silverlight 的標準組件以及 Windows Phone 平台專用的組件。


圖表 10
Solution Explorer 展示了被專案引用的組件集合

10. 專案的 Properties 視窗是編輯 WP 清單檔的唯一方式,正如下表所示。想打開這個視窗,在 Solution Explorer 中的 Windows Phone Puzzle 項目按右鍵然後選擇 Properties


圖表 11
專案屬性視窗

**說明:**Windows Phone 專案屬性視窗允許您更改一些電話程式專用的屬性。這些屬性關係到應用程式在電話設備上的部署和外觀效果。所有相關參數被儲存在 WMAppManifest.xml 文件中。即使您試圖手動更改過這個 XML 檔,只要在這個對話方塊中對您的項目進行更改設定並儲存時,其結果都會自動覆蓋您之前的手動更改。


任務 2 – 在 Windows Phone Emulator 中建置並測試應用程式

此時,應用程式並不能做什麼,但是它已經做好第一次測試運行的準備。在這個任務中,您會建置一個應用程式,並把它部署到 Windows Phone Emulator 然後執行它,通過這些您就能瞭解一個典型的開發過程。

1. 在 Debug 功能表,指向 Windows 然後透過選擇 Output 來打開 Output 視窗。

2. 在 Debug 功能表選擇 Build Solution 或者按鍵盤上的 SHIFT + F6 複合鍵來編譯解決方案中的專案。

**Visual Studio 2010:**在 Debug 功能表選擇 Build Solution 或者按鍵盤上的 CTRL + SHIFT + B 複合鍵來編譯解決方案中的專案。

3. 查看 Output 窗口查看編譯過程中產生的編譯器輸出記錄,包括最後輸出的結果資訊。


圖表 12
在 Visual Studio 中建置 (Build) 應用程式

4. 當下您應該不會發現任何錯誤,但是如果專案包含編譯錯誤,那麼這些錯誤資訊將會出現在 Output 視窗中。要處理這些類型的錯誤,您可以利用 Error List 視窗。這個視窗能夠以清單的形式顯示錯誤、警告以及編譯器產生的資訊,您可以根據錯誤的嚴重性來排序或者過濾。此外,你可以透過按兩下列表中的條目來自動打開相關原始程式碼檔並切換到錯誤的源點。要打開 Error List 視窗,在 View 功能表,指向 Other Windows 然後選擇 Error List

**Visual Studio 2010:**按 View 功能表,選擇 Error List 來打開 Error List 視窗。


圖表 13
Error List 視窗顯示建置過程中的錯誤

5. 驗證部署的目標是 Windows Phone Emulator 要做到這樣,確保在工具列上 Start Debugging 按鈕旁的 Select Target 下拉清單中的值是 Windows Phone 7 Emulator。


圖表 14
選擇目標設備來部署應用程式

**說明:**當您從 Visual Studio 中部署您開發的應用程式時,您可以選擇一個真實的設備或者是 Windows Phone Emulator。

6. 在 Windows Phone Emulator 上按 F5 來啟動應用程式。注意設備模擬器視窗的顯示,同時在 Visual Studio 建立模擬器環境和部署應用程式封裝時會有短時間的停頓。


圖表 15
部署一個應用程式封裝 (images) 到 Windows Phone Emulator

7. 一旦準備就緒,模擬器會顯示開始頁面,不久之後,您的應用程式也將會出現在模擬器的視窗中。


圖表 16
在 Windows Phone Emulator 中執行應用程式

8. 直到您完成了建立使用者介面以及應用程式邏輯的程式設計設計,否則您在當前應用程式的頁面基本上什麼都不能做。按下 SHIFT + F5 或者工具列上的 Stop 按鈕來停止除錯器並終止除錯程序。不要關閉模擬器視窗。


圖表 17
終止除錯程序

提示:當您啟動一個除錯程序時,它將會花費一些時間來設定模擬器環境以及啟動應用程式。為使您的除錯過程簡單而有效,當您在用 Visual Studio 編寫原始程式碼的時候,請避免關閉模擬器。如果模擬器一直處於執行狀態,它只會用很少的時間來完成停止目前程序、編輯原始程式碼、建置應用程式、部署新應用程式封裝、重新開機除錯程序這一整個過程。


任務 3 – 為主頁設計使用者介面

在這個任務中您將會為應用程式的主頁面建立使用者介面。這個頁面顯示一個版權頁。它包含一個代表應用程式的圖片以及離開版權頁導覽進入到遊戲頁面的按鈕。

1. 首先,在專案裡新建一個資料夾來保存應用程式中所使用的圖片。在 Solution Explorer 視窗中,按右鍵 WindowsPhonePuzzle 工程節點, 指向 Add 並選擇 New Folder。為建立好的資料夾命名為 Assets

**說明:**當您添加了一個新的資料夾,Solution Explorer 視窗中將會顯示一個以預留位置命名的資料夾,您可以透過輸入一個新的名稱來替換預留位置。如果您不小心放棄編輯模式,您可以按 F2 重新回到編輯模式。


圖表 18
Solution Explorer 視窗顯示新的 Assets 資料夾

2. 現在,在專案中添加應用程式所需的圖片。在 Solution Explorer 視窗上的 Assets 按右鍵,選  Add 然後選擇 Existing Item。在 Add Existing Item 對話方塊,按 browse 並瀏覽到實驗目錄下 Source 資料夾中的 Assets,然後按住 CTRL 鍵不放 ,用滑鼠按下 SplashImage.jpg  和 Puzzle.jpg,選擇這兩個圖檔後按 Add

**說明:**在 Visual Studio 中,您可以為自己的 Windows Phone 應用程式所建立的 Silverlight 專案添加資源檔,然後可以通過設置 Build Action 的值來完成對這些檔的部署。專案中每個被添加的圖片 Build Action 值都要被設為 Resource,這些圖片在進行專案編譯時會被嵌入檔案中成為內嵌資源 (Embedded Resources)。

3. 如果要查看某個圖片,您可以在 Solution Explorer 中在該圖片上按兩下,用在你機器上註冊的圖像編輯應用程式來打開它,例如 Paint.exe (小畫家)

**說明:**Visual Studio 2010中,在 Solution Explorer 中在該圖片上按兩下,該圖片會在內嵌的圖像編輯器中打開


圖表 19
應用套裝程式中的圖像嵌入資源

4. 打開應用程式的主頁面,在 Solution Explorer 視窗,於 MainPage.xaml 按兩下將其載入到 designer 中。

5. 在 Design 畫面,選擇 TextBlock 並將其命名為 ApplicationTitle,然後按 F4。在 Properties 視窗,更改 Text 屬性—將所有屬性按照類排序後,其位置在 Common 分類中—設定其值為 ‘WINDOWS PHONE PUZZLE’


圖表 20
在 Properties 視窗設計 UI 元素

**說明:**您可以通過按 Properties 視窗工具列上的圖示來為所有屬性進行按照類別或者屬性排序。

6. 接下來,選擇名為 PageTitle 的 TextBlock 並設定其 Text 屬性值為 ‘start’。

7. 在由 Windows Phone 應用範本產生的 XAML 標記程式中,找到名為 LayoutRoot 的 Grid 容器元素。它是用來佈置頁面上的元素。注意root Grid 元素包含其他嵌入的元素,這些元素透過定義一個Grid.Row 屬性被分配到外部 Grid 裡的不同行。找到名為 ContentPanelGrid 元素並將其 Grid.Row 設為 1,並插入下面藍底的 XAML 標記程式碼。

XAML

<Grid x:Name="LayoutRoot" Background="Transparent">
  ...
  <!--ContentPanel - place additional content here-->
  <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <Grid.RowDefinitions>
      <RowDefinition Height=".8*" />
      <RowDefinition Height=".2*" />
    </Grid.RowDefinitions>
    <Image Source="Assets/SplashImage.jpg" VerticalAlignment="Center" HorizontalAlignment="Center" Width="471" Height="492"  />
    <Button Content="START!" Name="StartButton" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center" />
  </Grid>
</Grid>

**說明:**上面的 XAML 標記程式碼建立了一個兩行的頁面配置,第一行包含了版權頁所需的圖片,第二行包含了一個用來退出當前頁面並切換到智力遊戲主頁面的按鈕。

8. 切換編輯模式到 Design 畫面。要把 Design 畫面最大化到視窗區域,按兩下設計工具視窗右邊緣上 Design 標籤 ( ) 。如果您不方便找到正確的標籤,可以把滑鼠停留在每個標籤上顯示提示資訊 (tooltip),從而找到正確的標籤。

**說明:**如果您的實際工具視窗選擇的是水準分隔視窗,則標籤將會被自動放置到視窗的下邊緣。

9. 為按鈕建立一個事件控制碼。為實現這一目的,按兩下設計工具表面上的 START 按鈕建立了一個 Click 事件控制碼並且會自動打開程式碼隱藏檔,此時游標會指到 StartButton_Click 事件控制碼的方法程式碼中。把以下 (藍底) 程式碼片段加到方法的程式碼中。

 (程式碼片段 – YourFirstWP7App –練習1  任務3  步驟9 – StartButton_Click事件控制碼)

C#

private void StartButton_Click(object sender, RoutedEventArgs e)
{
  // navigate
  this.NavigationService.Navigate(new Uri("/PuzzlePage.xaml", UriKind.Relative));
}

說明:PhoneApplicationPage 類別利用它的 NavigationService 屬性提供了切換到其他頁面的方法和屬性。您可以呼叫 NavigationServiceNavigate 方法並把頁面的 URL 做參數。您還可以使用 GoBackGoForward 方法在導航歷史中向前或向後導覽。硬體設備的後退按鍵透過應用程式也提供了向後翻頁導覽的功能。

上面的事件控制碼顯示了使用 NavigationService 轉移到 PuzzlePage.xaml 頁面。


任務 4 – 在應用程式中處理錯誤

在這個任務中,您將會透過升級應用程式來顯示例外不能被處理的錯誤頁面。要管理不能被處理的例外操作,您首先需要在應用程式中增加一個新的頁面來顯示錯誤的資訊。然後,您需要建立一個事件控制碼來處理 UnhandledException 事件。無論何時當應用程式不能捕捉一個例外的時候,都將觸發該事件在控制碼程式中,您導覽切換到一個錯誤頁面並將例外的錯誤資訊傳遞給該頁面。

**說明:**若不考慮如何實現一個未處理例外的程式碼,您的應用程式應該包含您所能處理的任何例外的操作。

1. 首先,添加一個新的頁面到專案中。在 Solution Explorer 視窗,於 Windows Phone Puzzle 節點按右鍵,指向 Add 並選擇 New Item。在 Add New Item 對話方塊,從範本清單裡選擇 Windows Phone Portrait Page,把 name 設為 ErrorPage.xaml 然後按 Add


圖表 21
為專案添加一個新的頁面

2. 在 ErrorPage.xaml 檔中,找到名為 LayoutRoot 的 Grid 元素,並用下面藍底標示的 XAML 標記程式碼替換它的子控制項。這個 XAML 定義了一個應用程式標題和頁面標題,兩個標題都被命名為 error。它同樣定義了一個 TextBlock 物件並被指定為 x:Name="ErrorText" 樣式,用來保存未來任何例外的錯誤內容。

XAML

...

<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <!--TitlePanel contains the name of the application and page title-->
    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="24,24,0,12">
        <TextBlock x:Name="ApplicationTitle" Text="WINDOWS PHONE PUZZLE" Style="{StaticResource PhoneTextNormalStyle}"/>
        <TextBlock x:Name="PageTitle" Text="error" Margin="-3,-8,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>
    <!--ContentPanel - place additional content here-->
    <Grid x:Name="ContentPanel" Grid.Row="1">
        <Border BorderBrush="White">
            <TextBlock x:Name="ErrorText" Style="{StaticResource PhoneTextSmallStyle}" TextWrapping="Wrap" />
        </Border>
    </Grid>
</Grid>

...

3. 現在,按下 F7 來打開一個新頁面的程式碼隱藏檔。或者,你有另一種選擇,在 Solution Explorer 視窗上的 ErrorPage.xaml 按右鍵然後選擇 View Code。在檔案的最上層插入下面的命名空間。

C#

using System.Windows.Navigation;

4. 然後,把下面程式碼片段中藍底標示的部分插入到 ErrorPage 類別中。這樣做就建立了一個 Exception 物件,當切換到這個頁面的時候會自動設定到 ErrorText.Text。

(程式碼片段 – YourFirstWP7App練習1任務4步驟4 –錯誤頁面OnNavigatedTo)

C#

public partial class ErrorPage : PhoneApplicationPage
{
  public ErrorPage()
  {
    InitializeComponent();
  }

  public static Exception Exception;

  // Executes when the user navigates to this page.
  protected override void OnNavigatedTo(NavigationEventArgs e)
  {
    ErrorText.Text = Exception.ToString();
  }
}

5. 當這個頁面完成後,無論何時,當一個不能被處理的例外出現,此類事件的控制碼會使程式切換到錯誤的頁面,並顯示錯誤的資訊。在 Solution Explorer 視窗,於 App.xaml 按右鍵並選擇 View Code 來打開 Application 類別的後置程式碼。

6. 在 App 類別中,找到 Application_UnhandledException 事件處理常式並用下面的程式碼 (藍色標示部分) 替換處理常式的程式碼。

 (程式碼片段 – YourFirstWP7App –練習1任務4步驟6 – Application_UnhandledException)

C#

// Code to execute on Unhandled Exceptions
private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
  if (System.Diagnostics.Debugger.IsAttached)
  {
    // An unhandled exception has occurred; break in the debugger
    System.Diagnostics.Debugger.Break();
  }
  
  e.Handled = true;
  ErrorPage.Exception = e.ExceptionObject;
  (RootVisual as Microsoft.Phone.Controls.PhoneApplicationFrame).Source = 
      new Uri("/ErrorPage.xaml", UriKind.Relative);
}

**說明:**這個 Application_UnhandledException 就像一張安全網,您應用程式中所有的不能被處理的例外都在這裡終止。UnhandledException 事件控制碼處理完成後,它把所有的 Handled 屬性都設定為 true,這樣做是為了阻止對例外的進一步處理。然後它把未處理例外的資訊保存到 ErrorPage 類的靜態成員中,並設定畫面的 Source 屬性來顯示錯誤頁面。當您設定 Source 屬性的值與顯示內容不同時,那麼顯示的畫面將會切換為新的內容。當您切換到錯誤的頁面後,它將會傳回例外物件的內容值 (Exception.ToString()) 並以此顯示到頁面中。一旦您開始在真正設備上除錯您的應用程式,這將會非常有用。


任務 5 – 驗證

在這個任務中,你開始建置這個應用程式,並將其部署到 Windows Phone Emulator,執行該應用成來驗證你之前的操作是否正確。

1. 在 Visual Studio 中,按 CTRL +F5 把應用程式部署到 Windows Phone Emulator 並執行它 (不是除錯)。等待應用程式啟動並顯示其主頁面。它將會顯示版權頁。


圖表 22
執行在模擬器上的應用程式的版權頁

2. 點擊 START 按鈕。注意應用程式顯示了您之前定義的不能處理例外的錯誤頁面。請記住這是所期望得到的結果,因為按鈕 Click 事件的控制碼是將會切換螢幕到 PuzzlePage.xaml 頁面,這是一個目前還沒有定義的頁面。您將會在後面的實驗中定義該頁面。


圖表 23
未處理例外錯誤頁面

3. 按兩下 Back 按鈕回到電話的主頁面。到目前位置,除非為應用程式增加新的功能,否則您所能做的非常有限。請不要關閉模擬器視窗。


練習2:在 Visual Studio 中建立拼圖板

在這個任務中,您將會建立一個頁面來顯示拼圖板 (Puzzle) 和玩遊戲。這個頁面顯示了一個圖片,當點擊它是,圖片會碎成多片同時會在拼圖板中隨機排列。

這個遊戲的邏輯已經包含在了 PuzzleGame 類別中,您可以使用實驗提供的資源包添加這個類別到專案中。在這個練習中,您會建立頁面上控制項的佈局,添加必需的應用程式邏輯來初始化拼圖板以及回應使用者介面上的事件。接下來,您需要添加多點觸控功能來支援用戶使用手指來拖曳拼圖板上每一片拼圖並重新排列它們。最後,您將使用動畫腳本 (storyboard) 來建立完成拼圖時的引人注目的視覺效果。

任務 1 – 建立使用者介面

在這個任務中,您會為應用程式添加一個新的頁面來顯示拼圖板,還會添加必需的使用者介面元素來顯示拼圖的圖片以及遊戲過程中已經移動拼圖次數的狀態面板。在頁面中,還會有一個按鈕,當按下它時,它將會自動幫您完成拼圖。

1. 如果還沒有打開,從 Start | All Programs | Microsoft Visual Studio 2010 Express for Windows Phone 啟動用於 Windows Phone 的 Microsoft Visual Studio 2010 Express。

**Visual Studio 2010:**從作業系統的 Start | All Programs | Microsoft Visual Studio 2010 啟動 Visual Studio 2010。

2. 如果您完成了前一個練習的所有步驟,您可以繼續使用為上個練習所建立的解決方案 (solution);否則,從實驗的 Source 資料夾下的 Hello Phone \ Ex2-WP7AppUXDesignWithBlend \ Begin 處打開  Begin. sln

3. 為工程添加一個新的頁面。在 Solution Explorer 畫面,在 WindowsPhonePuzzle 專案節點按右鍵,指向 Add 並選擇 **New Item。**在 Add New Item 對話方塊,從範本清單中選擇 Windows Phone Portrait 頁面,把 name 設定為 PuzzlePage.xaml 然後按 Add


圖表 24
為工程添加一個新的頁面

4. 在新頁面的 XAML 視窗中,找到 LayoutRoot 元素的 RowDefinitions 部分,將其第一行 Height 屬性的值設為 0.2*.


圖表 25
配置 Grid 容器的頁面設定

**說明:**指定高度的值為*,是為了確保當 Grid 的佈局中其行都已經被分配後,該行能夠自動伸縮來填滿可用空間。當高度為星 (*) 且包含一個乘數時,如在這個例子中第一行高度的定義,未被使用的空間基於乘數值被按比例分配給所有被標注為“start-sized”的行 — 乘數是 1 則假設為不指定。這樣,在上面的定義中,第一行佔有 1 / 6 的剩餘空間。

5. 在名為 LayoutRoot 的 grid 中,刪除預設範本中存在的 TitlePanelContentPanel 元素 — 包括它的子元素部分。

6. 在 RowDefinitions 部分的下方,插入兩個 StackPanel 容器元素,如下面藍底標示的部分,每個元素對應佈局中的一行。

XAML

<Grid x:Name="LayoutRoot" Background="Transparent">
  <Grid.RowDefinitions>
    <RowDefinition Height="0.2*"/>
    <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
  <StackPanel Orientation="Vertical" VerticalAlignment="Stretch">
  </StackPanel>
  <StackPanel Orientation="Vertical" VerticalAlignment="Top" Grid.Row="1">
  </StackPanel>
</Grid>

7. 插入第一個 StackPanel 元素 — 被預設指定給 LayoutRoot Grid 容器最高一行 (第 0 行) — 插入以下藍底標示部分的 XAML 標記程式碼。

XAML

...
<Grid x:Name="LayoutRoot" Background="Transparent">
  ...
  <StackPanel Orientation="Vertical" VerticalAlignment="Stretch">
    <Button x:Name="SolveButton" Content="SOLVE" Margin="10" HorizontalAlignment="Center" Click="SolveButton_Click" />
    <StackPanel x:Name="StatusPanel" Orientation="Horizontal" HorizontalAlignment="Center" Visibility="Collapsed">
      <TextBlock HorizontalAlignment="Center" Text="Your Moves: " TextWrapping="Wrap" Foreground="#FFD0D0D0" FontSize="17.333"/>
      <TextBlock x:Name="TotalMovesTextBlock" HorizontalAlignment="Center" Text="N" TextWrapping="Wrap" Foreground="#FFFFB000" FontSize="17.333"/>
    </StackPanel>
  </StackPanel>
  <StackPanel Orientation="Vertical" VerticalAlignment="Top" Grid.Row="1">
  </StackPanel>
</Grid>
...

**說明:**藍底標示的 XAML 標記程式碼定義了一個按鈕,當您按下時它將會自動解謎拼圖並會在狀態面板中顯示完成遊戲一共移動了多少步。

8. 然後, 在 Grid 佈局中最下面一行即第二個 StackPanel 元素中,插入以下藍底標示  XAML 標記程式碼。

XAML

...
<Grid x:Name="LayoutRoot" Background="Transparent">
  ...
  <StackPanel Orientation="Vertical" VerticalAlignment="Top" Grid.Row="1">
    <Border x:Name="CongratsBorder" Height="30" Background="#FFF10DA2" HorizontalAlignment="Center" Width="443" RenderTransformOrigin="0.5,0.5" UseLayoutRounding="False" Opacity="0">
      <Border.RenderTransform>
        <TransformGroup>
          <ScaleTransform/>
          <SkewTransform/>
          <RotateTransform/>
          <TranslateTransform/>
        </TransformGroup>
      </Border.RenderTransform>
      <TextBlock HorizontalAlignment="Center" Text="CONGRATULATIONS!" TextWrapping="Wrap" Foreground="White" FontSize="17.333" VerticalAlignment="Center" FontWeight="Bold"/>
    </Border>
  </StackPanel>
</Grid>

...

**說明:**這個 Border 元素中包含了當拼圖完成時將會被顯示的一個訊息。注意 Border 中包含了 RenderTransform。這個形變 (transformation) 是後面實驗中建立動畫腳本時所需要的,目的是為了提供一個視覺效果。動畫腳本不能使一個元素動起來,除非它發生了形變。

9. 為了下面那個 StackPanel 元素的內容, 接著上一步插入的程式碼後面再次插入下面的藍底標示 XAML 標記程式碼。

XAML

...
<Grid x:Name="LayoutRoot" Background="Transparent">
  ...
  <StackPanel Orientation="Vertical" VerticalAlignment="Top" Grid.Row="1">
    <Border x:Name="CongratsBorder"...>
    </Border>
    <Border x:Name="border" BorderThickness="3" Background="#FF262626" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="1" RenderTransformOrigin="0.5,0.5">
      <Border.RenderTransform>
        <TransformGroup>
          <ScaleTransform/>
          <SkewTransform/>
          <RotateTransform/>
          <TranslateTransform/>
        </TransformGroup>
      </Border.RenderTransform>
      <Border.BorderBrush>
        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
          <GradientStop Color="#FFF10DA2" Offset="0"/>
          <GradientStop Color="#FFEE7923" Offset="1"/>
        </LinearGradientBrush>
      </Border.BorderBrush>
      <Canvas Height="435" Width="435">
        <Image x:Name="PreviewImage" Height="435" Width="435" Opacity="0.2" />
        <Canvas x:Name="GameContainer" Width="435" Height="435" />
      </Canvas>
    </Border>
    <TextBlock x:Name="TapToContinueTextBlock" HorizontalAlignment="Center" Text="Tap the picture to start the puzzle" TextWrapping="Wrap" Foreground="#FFD0D0D0" FontSize="17.333"/>
  </StackPanel>
</Grid>

**說明:**上面的 XAML 標記定義了一個 Border 元素,實現用帶有多種顏色的漸變線性畫刷來畫圖,且這些顏色彼此融入。這個元素還指定了一個 RenderTransform,目的是為了建立動畫腳本來實現視覺效果。

Border 元素中有一個單獨的 Canvas 元素。這個容器元素被用來明確指定其它子元素相對在該容器元素中的座標位置,它的子元素中包含有一個 Image 元素,用一浮水印圖片來預覽拼圖解決方案 (PreviewImage)。還包含了一個內部的 Canvas (GameContainer),用來保存拼圖片。

標記中還包含了一個 TextBlock 元素 (TapToContinueTextBlock) 顯示了如何啟動一個遊戲。當遊戲進行時,該元素就會被隱藏。

10. 切換到設計模式 (Design) 來檢查頁面的佈局。要選擇視窗並切換到最大可視區域 (即最大化),按兩下設計工具 (designer) 視窗的右邊緣上的 Design 標籤 ( )。如果您不能確定正確的標籤,利用滑鼠游標停留到每個表上會顯示提示資訊的辦法來找到正確的標籤。頁面應該出現並顯示成如下圖:


圖表 26
Windows Phone Puzzle 應用程式使用者介面


任務 2 – 設計程式,實作應用程式邏輯

在這個任務中,您將會設計程式以實作應用程式邏輯。包括初始化拼圖板,從應用程式資源中讀取圖片和為使用者介面上的事件建立處理常式。

1. 在專案中添加包含遊戲邏輯的類別。在 Solution Explorer上,右鍵點擊 Windows Phone Puzzle 工程節點,指向 Add 並選擇 Existing Item. 在 Add Existing Item 對話方塊,按 browse 並瀏覽到實驗下 Source 資料夾中的 Assets,選擇 PuzzleGame.cs 然後按一下 Add。


圖表 27
Solution Explorer 顯示已經添加的專案檔

**說明:**遊戲的邏輯是被包含在 PuzzleGame 類別中,您可以從實驗中的資源包中找到該類別並添加到工程中。這個類別提供了一些方法,包括開一個新遊戲,移動拼圖片,保存和返回遊戲狀態。您可以自由的查看整個類別中的原始程式碼,但是詳細地分析該程式碼已經超出了本實驗的範圍。

2. 打開你在前面任務建立的拼圖頁面的後置程式碼檔。要實現這一目的,在 Solution Explorer 上,在 PuzzlePage.xaml 檔按右鍵,並選擇 View Code

3. 把以下命名空間的宣告添加到 PuzzlePage.xaml.cs 後置程式碼中。

(程式碼片段 – YourFirstWP7App –練習2任務2步驟3 – PuzzlePage命名空間)

C#

using System.IO;
using System.Windows.Media.Imaging;
using System.Windows.Resources;

4. 在 PuzzlePage 類別,插入下面成員變數的宣告 (藍底標示部份)。

(程式碼片段 – YourFirstWP7App –練習2任務2步驟4 – PuzzlePage成員變數)

C#

public partial class PuzzlePage : PhoneApplicationPage
{
  private const double DoubleTapSpeed = 500;
  private const int ImageSize = 435;
  private PuzzleGame game;
  private Canvas[] puzzlePieces;
  private Stream imageStream;

  public PuzzlePage()
  {
    InitializeComponent();
  }
}

5. 現在,添加一個 ImageStream 屬性,如下面程式碼片段所示 (藍底標示部分)。

(程式碼片段 – YourFirstWP7App –練習2任務2步驟5 – ImageStream屬性)

C#

public partial class PuzzlePage : PhoneApplicationPage
{
  ...
  public Stream ImageStream
  {
    get
    {
      return this.imageStream;
    }

    set
    {
      this.imageStream = value;

      BitmapImage bitmap = new BitmapImage();
      bitmap.SetSource(value);
      this.PreviewImage.Source = bitmap;

      int i = 0;
      int pieceSize = ImageSize / this.game.ColsAndRows;
      for (int ix = 0; ix < this.game.ColsAndRows; ix++)
      {
        for (int iy = 0; iy < this.game.ColsAndRows; iy++)
        {
          Image pieceImage = this.puzzlePieces[i].Children[0] as Image;
          pieceImage.Source = bitmap;
          i++;
        }
      }
    }
  }

  public PuzzlePage()
  {
    InitializeComponent();
  }
}

說明:ImageStream 屬性傳回拼圖圖片的資料流。它自動更新背景圖片以及每個拼圖碎片。您可以通過設定該屬性值為任何一個有效的點陣圖來實現替換拼圖片,例如,一張來自手機相機所拍中的圖片。在這個實驗手冊中,你可以使用應用套裝程式的內嵌資源。

6. 利用下面程式碼片段 (藍底標示部分) 更新 PuzzlePage 類的建構函數。

(程式碼片段 – YourFirstWP7App –練習2任務2步驟6 – PuzzlePage建置函數)

C#

public partial class PuzzlePage : PhoneApplicationPage
{
  ...
  public PuzzlePage()
  {
    InitializeComponent();

    SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;

    // Puzzle Game
    this.game = new PuzzleGame(3);

    this.game.GameStarted += delegate
    {
      this.StatusPanel.Visibility = Visibility.Visible;
      this.TapToContinueTextBlock.Opacity = 0;
      this.TotalMovesTextBlock.Text = this.game.TotalMoves.ToString();
    };

    this.game.GameOver += delegate
    {
      this.TapToContinueTextBlock.Opacity = 1;
      this.StatusPanel.Visibility = Visibility.Visible;
      this.TotalMovesTextBlock.Text = this.game.TotalMoves.ToString();
    };

    this.game.PieceUpdated += delegate(object sender, PieceUpdatedEventArgs args)
    {
      int pieceSize = ImageSize / this.game.ColsAndRows;
      this.AnimatePiece(this.puzzlePieces[args.PieceId], Canvas.LeftProperty, (int)args.NewPosition.X * pieceSize);
      this.AnimatePiece(this.puzzlePieces[args.PieceId], Canvas.TopProperty, (int)args.NewPosition.Y * pieceSize);
      this.TotalMovesTextBlock.Text = this.game.TotalMoves.ToString();
    };

    this.InitBoard();
  }
}

**說明:**建構函數產生實體遊戲的邏輯 — 被 PuzzleGame 類別封裝 — 然後繫結其事件,PuzzleGame 類別定義了如下事件:

GameStarted:當遊戲開始後這個事件就被觸發。這個事件的程式碼展示了帶有圖片被移動次數統計的面板,隱藏了如何開始遊戲的說明並重置了已經移動拼圖片的次數統計。

GameOver:當拼圖完成時這個事件就會發生。這個事件的程式碼展示了如何運行遊戲的圖例說明,並更新了移動次數的統計。

PieceUpdated:無論何時當一個拼圖片被移動,這個事件就會被觸發。這個事件的控制碼使評圖片動起來並更新了移動次數的統計。

最後,通過預訂一個事件程式碼,建構函式呼叫 InitBoard 來初始化拼圖遊戲台。

7. 接著,在 PuzzlePage 類別,定義 InitBoard 方法來初始化遊戲台。

(程式碼片段– YourFirstWP7App –練習2任務2步驟7 – InitBoard方法)

C#

private void InitBoard()
{
  int totalPieces = this.game.ColsAndRows * this.game.ColsAndRows;
  int pieceSize = ImageSize / this.game.ColsAndRows;

  this.puzzlePieces = new Canvas[totalPieces];
  int nx = 0;
  for (int ix = 0; ix < this.game.ColsAndRows; ix++)
  {
    for (int iy = 0; iy < this.game.ColsAndRows; iy++)
    {
      nx = (ix * this.game.ColsAndRows) + iy;
      Image image = new Image();
      image.SetValue(FrameworkElement.NameProperty, "PuzzleImage_" + nx);
      image.Height = ImageSize;
      image.Width = ImageSize;
      image.Stretch = Stretch.UniformToFill;
      RectangleGeometry r = new RectangleGeometry();
      r.Rect = new Rect((ix * pieceSize), (iy * pieceSize), pieceSize, pieceSize);
      image.Clip = r;
      image.SetValue(Canvas.TopProperty, Convert.ToDouble(iy * pieceSize * -1));
      image.SetValue(Canvas.LeftProperty, Convert.ToDouble(ix * pieceSize * -1));

      this.puzzlePieces[nx] = new Canvas();
      this.puzzlePieces[nx].SetValue(FrameworkElement.NameProperty, "PuzzlePiece_" + nx);
      this.puzzlePieces[nx].Width = pieceSize;
      this.puzzlePieces[nx].Height = pieceSize;
      this.puzzlePieces[nx].Children.Add(image);
      this.puzzlePieces[nx].MouseLeftButtonDown += this.PuzzlePiece_MouseLeftButtonDown;
      if (nx < totalPieces - 1)
      {
        this.GameContainer.Children.Add(this.puzzlePieces[nx]);
      }
    }
  }

  // Retrieve image
  StreamResourceInfo imageResource = Application.GetResourceStream(new Uri("WindowsPhonePuzzle;component/Assets/Puzzle.jpg", UriKind.Relative));
  this.ImageStream = imageResource.Stream;

  this.game.Reset();
}

說明:InitBoard 方法建立了一個 Canvas 控制項,該控制項包含了拼圖的所有的拼圖片。每一片都是遊戲中整幅圖片的裁剪出的一部分。

這個方法從應用程式的資源中取得拼圖遊戲的圖片並利用結果資料流初始化 ImageStream 屬性。

8. 插入 AnimatePiece 方法到 PuzzlePage 類別中。

(程式碼片段 – YourFirstWP7App –練習2任務2步驟8 – AnimatePiece方法)

C#

private void AnimatePiece(DependencyObject piece, DependencyProperty dp, double newValue)
{
    Storyboard storyBoard = new Storyboard();
    Storyboard.SetTarget(storyBoard, piece);
    Storyboard.SetTargetProperty(storyBoard, new PropertyPath(dp));
    storyBoard.Children.Add(new DoubleAnimation
    {
        Duration = new Duration(TimeSpan.FromMilliseconds(200)),
        From = Convert.ToInt32(piece.GetValue(dp)),
        To = Convert.ToDouble(newValue),
        EasingFunction = new SineEase()
    });
    storyBoard.Begin();
}

說明:AnimatePiece 是一個程式碼方法用來實現使用者介面中拼圖碎片的動畫。它利用程式碼建立了一個動畫腳本,並用該腳本更新控制項中的任何一個 DependencyProperty屬性。在這個應用程式中,此方法僅被用來更新拼圖碎片的 TopLeft 屬性,並以此來控制碎片的位置。

9. 為 MouseLeftButtonDown 事件增加程式碼。為了實現這個目的,插入以下程式碼到 PuzzlePage 類別中。

(程式碼片段 – YourFirstWP7App –練習2任務2步驟9 – PuzzlePiece_MouseLeftButtonDown事件控制碼)

C#

private void PuzzlePiece_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
  if (!this.game.IsPlaying)
  {
    this.game.NewGame();
  }
}

**說明:**當按拼圖板表面將會觸發該事件的程式碼,除非當前遊戲還在進行,否則將會開啟一個新的遊戲。

10. 現在,為“Solve”按鈕添加 Click 事件的程式碼。

(程式碼片段 – YourFirstWP7App –練習2任務2步驟10 – SolveButton_Click事件控制碼)

C#

private void SolveButton_Click(object sender, RoutedEventArgs e)
{
    this.game.Reset();
    this.game.CheckWinner();
}

**說明:**按鈕的事件程式碼會強制遊戲重置 - 顯示完成拼圖的原始圖片 - 然後呼叫 PuzzleGame 類別中的 PuzzleGame.CheckWinner 方法。這個方法驗證每張拼圖碎片是否處於正確的位置,並會觸發 GameOver 事件,GameOver 事件會使使用者介面顯示祝賀資訊。

11. 按 F5 來建置並部署應用程式到 Windows Phone Emulator 中,等待版權頁面出現並點擊 START 鍵**。**


圖表 28
開始一個新遊戲

12. 在模擬器視窗,按下圖片來開始一個新的拼圖遊戲。注意整張圖片被分裂成碎片,這些碎片被隨機分佈在拼圖板中。此外,觀察碎片不是迅速被打亂到各個位置,而是由於使用 AnimatePiece 方法中呼叫的動畫腳本,使得碎片緩慢平滑的進入到各個位置。這個方法實現對每個拼圖碎片 left 和 top 座標的移動,從而實現觀察到的變化效果。


圖表 29
當遊戲開始後拼圖板上的拼圖碎片被隨機重新排列

13. 選中一張碎片並嘗試拖曳它。注意目前拖曳操作是無效的。在下一個任務中,您將會添加對 multi-touch 多點觸控的支援,並會允許您在拼圖板中拖曳碎片來重新排列它們。

14. 現在,點擊 SOLVE 按鈕,此時可以看到拼圖板上的碎片重新排列並顯示原始的完成圖片。

15. 在 Visual Studio 中,按下 SHIFT + F5 停止除錯過程。請不要關閉模擬器。


任務 3 – 增加 Multi-Touch 支援

Multi-touch 輸入允許用戶同時進行多指操作,這些操作被看成是一個單元來為應用程式提供複雜的操作命令,從而模擬直接操控頁面上的元素,例如,同時平移和縮放。在這個任務中,您會更新 Windows Phone Puzzle 遊戲,使其能夠接收 multi-touch 的輸入並允許用戶在拼圖板上通過按下拼圖碎片然後拖曳到空格 (empty slot) 中。

1. 在 Solution Explorer 中,在 designer 中透過在 PuzzlePage.xaml 上按右鍵,並選擇 View Code 來打開該頁面的後置程式碼檔案。

2. 在 PuzzlePage 類別中,在已經存在的成員變數下插入下面的宣告程式碼 (藍底標示部分)。

(程式碼片段 – YourFirstWP7App –練習2任務3步驟2 – Multi-Touch成員變數)

C#

public partial class PuzzlePage : PhoneApplicationPage
{
  private const double DoubleTapSpeed = 500;
  private const int ImageSize = 435;
  private PuzzleGame game;
  private Canvas[] puzzlePieces;
  private Stream imageStream;

  private long lastTapTicks;
  private int movingPieceId = -1;
  private int movingPieceDirection;
  private double movingPieceStartingPosition;

  public PuzzlePage()
  {
    InitializeComponent();
    ...
}

3. 現在,在編輯視窗按右鍵,選擇 View Designer ( ),切換到 Design 視窗。

4. 在 designer 表面上按下圍繞模擬器的空白區域來選擇 PhoneApplicationPage 元素並按下 F4 打開 Propertie 視窗。在 Properties 視窗,按 Events 標籤來顯示所有可用事件。

**說明:**請小心不要選擇 designer 上另一個使用者介面元素,否則將會顯示錯選元素的 properties 視窗。


圖表 30
在 Microsoft Visual Studio 2010 Express 中為 Windows Phone 建立事件處理常式

5. 為 ManipulationStarted 事件定義一個事件處理常式。為實現這個目的,在事件清單裡按兩下相應的項目以產生該事件處理常式。當您這樣操作時,Visual Studio 會建立一個事件處理常式並打開其程式碼隱藏檔中生成的方法。把下面的程式碼 (藍底標示部分) 複製粘貼到 PhoneApplicationPage_ManipulationStarted 方法內。

(程式碼片段 – YourFirstWP7App –練習2任務3步驟5 – PhoneApplicationPage_ManipulationStarted事件控制碼)

C#

private void PhoneApplicationPage_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
{
  if (this.game.IsPlaying && e.ManipulationContainer is Image && e.ManipulationContainer.GetValue(FrameworkElement.NameProperty).ToString().StartsWith("PuzzleImage_"))
  {
    int pieceIx = Convert.ToInt32(e.ManipulationContainer.GetValue(FrameworkElement.NameProperty).ToString().Substring(12));
    Canvas piece = this.FindName("PuzzlePiece_" + pieceIx) as Canvas;
    if (piece != null)
    {
      int totalPieces = this.game.ColsAndRows * this.game.ColsAndRows;
      for (int i = 0; i < totalPieces; i++)
      {
        if (piece == this.puzzlePieces[i] && this.game.CanMovePiece(i) > 0)
        {
          int direction = this.game.CanMovePiece(i);
          DependencyProperty axisProperty = (direction % 2 == 0) ? Canvas.LeftProperty : Canvas.TopProperty;
          this.movingPieceDirection = direction;
          this.movingPieceStartingPosition = Convert.ToDouble(piece.GetValue(axisProperty));
          this.movingPieceId = i;
          break;
        }
      }
    }
  }
}

**說明:**當使用者接觸 (或用滑鼠點按) 任何一個 UIElement 並操縱它時,會觸發 ManipulationStarted 事件。這個事件的控制碼會驗證當前遊戲是否正在運行,一個代表拼圖碎片的圖像元素是否觸發了這個事件,否則程式碼將會忽略此事件。該事件的程式碼還會定位與拼圖碎片對應的 Canvas 元素並呼叫遊戲的邏輯來判斷該碎片是否可以被移動。要做到這一點,拼圖碎片旁必須有一個空槽。最後它會儲存被選中的拼圖碎片並記錄碎片移動的坐標軸 / 方向。

6. 接著,為 ManipulationDelta 事件建立程式碼。請再次於程式碼編輯視窗按右鍵,並選擇 View Desinger,切換到 Design 視窗。然後,選擇 designer 上的 PhoneApplicationPage 元素,按下 F4 打開其 Properties 視窗,選擇 Events 標籤。接著,在事件列表中找到 ManipulationDelta 事件,按兩下其對應的項目來產生事件並由此產生了一個處理常式。在後置程式碼中,插入以下程式碼 (藍底標示部分) 到 PhoneApplicationPage_ManipulationDelta 方法中。

(程式碼片段 – YourFirstWP7App –練習2任務3步驟6 – PhoneApplicationPage_ManipulationDelta事件控制碼)

C#

private void PhoneApplicationPage_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
  if (this.movingPieceId > -1)
  {
    int pieceSize = ImageSize / this.game.ColsAndRows;
    Canvas movingPiece = this.puzzlePieces[this.movingPieceId];

    // validate direction
    DependencyProperty axisProperty;
    double normalizedValue;

    if (this.movingPieceDirection % 2 == 0)
    {
      axisProperty = Canvas.LeftProperty;
      normalizedValue = e.CumulativeManipulation.Translation.X;
    }
    else
    {
      axisProperty = Canvas.TopProperty;
      normalizedValue = e.CumulativeManipulation.Translation.Y;
    }

    // enforce drag constraints
    // (top or left)
    if (this.movingPieceDirection == 1 || this.movingPieceDirection == 4)
    {
      if (normalizedValue < -pieceSize)
      {
        normalizedValue = -pieceSize;
      }
      else if (normalizedValue > 0)
      {
        normalizedValue = 0;
      }
    }
    // (bottom or right)
    else if (this.movingPieceDirection == 3 || this.movingPieceDirection == 2)
    {
      if (normalizedValue > pieceSize)
      {
        normalizedValue = pieceSize;
      }
      else if (normalizedValue < 0)
      {
        normalizedValue = 0;
      }
    }

    // set position
    movingPiece.SetValue(axisProperty, normalizedValue + this.movingPieceStartingPosition);
  }
}

**說明:**當用戶在手機觸控式螢幕上移動其手指 (或者滑鼠點按) 來操縱一個 UIElement 時,ManipulationDelta 事件就會被觸發。這個事件的程式碼會檢查當前拼圖碎片是否正被移動。如果是,它將會捕捉唯一可能移動的坐標軸 / 方向上的 delta 值。要做到這一點, 程式碼需要保證拖曳是有效的,即所有的移動操作需要在可用邊界內進行 — 一個拼圖碎片不能和其他碎片重疊。然後這個程式碼會透過為適當的坐標軸設置屬性來實現對碎片位置的更新。

7. 最後,重複前面的步驟,這次選擇 ManipulationCompleted 事件,在後置程式碼中,插入以下程式碼 (藍底標示部分) 到 PhoneApplicationPage_ManipulationCompleted 方法中。

(程式碼片段 – YourFirstWP7App –練習2任務3步驟7 – PhoneApplicationPage_ManipulationCompleted事件控制碼)

C#

private void PhoneApplicationPage_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
  if (this.movingPieceId > -1)
  {
    int pieceSize = ImageSize / this.game.ColsAndRows;
    Canvas piece = this.puzzlePieces[this.movingPieceId];

    // check for double tapping
    if (TimeSpan.FromTicks(DateTime.Now.Ticks - this.lastTapTicks).TotalMilliseconds < DoubleTapSpeed)
    {
      // force move
      this.game.MovePiece(this.movingPieceId);
      this.lastTapTicks = int.MinValue;
    }
    else
    {
      // calculate moved distance
      DependencyProperty axisProperty = (this.movingPieceDirection % 2 == 0) ? Canvas.LeftProperty : Canvas.TopProperty;
      double minRequiredDisplacement = pieceSize / 3;
      double diff = Math.Abs(Convert.ToDouble(piece.GetValue(axisProperty)) - this.movingPieceStartingPosition);

      // did it get halfway across?
      if (diff > minRequiredDisplacement)
      {
        // move piece
        this.game.MovePiece(this.movingPieceId);
      }
      else
      {
        // restore piece
        this.AnimatePiece(piece, axisProperty, this.movingPieceStartingPosition);
      }
    }

    this.movingPieceId = -1;
    this.movingPieceStartingPosition = 0;
    this.movingPieceDirection = 0;
    this.lastTapTicks = DateTime.Now.Ticks;
  }
}

**說明:**當用戶完成對一個 UIElement 的操作,從手機觸控式螢幕上抬起手指 (或者釋放按下的滑鼠按鈕) 時將會觸發 ManipulationCompleted 事件,這個事件與 MouseUp 事件有些相似。這個事件的控制碼會檢查拼圖碎片當前是否正在被移動。如果是,它會判斷上次 ManipulationCompleted 事件是什麼時間被觸發的,假如時間間隔比 DoubleTapSpeed 所指定的值低,它會被此事件理解為 Double Tap 事件 (或滑鼠按兩下) 並把此拼圖碎片移動到鄰近的空槽裡。或者,它將計算拼圖碎片與其目標位置之間的偏移距離,如果拼圖碎片已經至少有三分之一的部分被移動到附近的槽裡時,它將會完成碎片到空槽的移動;如果沒有,它將會把拼圖碎片拉回到原來的位置。

8. 完成上述步驟後,打開頁面的 Properties 視窗,檢查您建立的事件程式碼是否正確。如果您查看頁面的 XAML 標記程式碼,您將會看到一些屬性值,並且與 PhoneApplicationPage 元素中定義的屬性相對應。


圖表 31
Properties 視窗顯示了操作的事件處理常式


圖表 32
頁面的 XAML 標記程式碼 顯示了事件常式的設定

9. 要測試添加的 multi-touch 效果,需要按 F5 來建置和部署應用程式到模擬器上。等待版權頁面的出現,然後點擊 START 來開始一個新的遊戲。

10. 在拼圖板上拖曳圖片碎片來完成遊戲。要想移動一個拼圖碎片,點按選擇該碎片並把它拖曳到目標位置。或者您還可以透過按兩下一個拼圖碎片,使其移動到旁邊可用的槽中。注意每次移動都會使拼圖面板上的計數器增加。


圖表 33
解謎完成拼圖

11. 繼續移動拼圖碎片直到遊戲完成,您的遊戲體驗像是應用程式正運行在一個有觸控式螢幕的真正設備上。當您感覺一切正常時,回到 Visual Studio 中按下 SHIFT + F5 來結束除錯程序。


任務 4 – 建立動畫效果

在 Silverlight 中,一個腳本 (storyboard) 就是一個動畫。每一個腳本定義了包含有關鍵框 (key frame) 的時間軸。每一個關鍵框可以獨立地重新定義控制項屬性,例如位置、大小、旋轉、透明度甚至包括對前景和背景色的定義。利用這種方法,使用者不需要通過定義每一個框來實現建立動畫的操作,取而代之的是只需要提供時間軸上的選擇點以及在該點標記重要屬性的改變。Silverlight 在相鄰兩個關鍵框之間插入一些動畫屬性的值,從而生成一些中間框並以此提供關鍵框之間平滑的過渡期。

每一個腳本就是方法和事件的物件,您可以從頁面的程式碼隱藏檔中找到它。它包含是一個動畫啟動停止暫停的方法以及一個單獨事件完成,這個事件出現在動畫停止播放的時候。

腳本可以被寫成 XAML 程式碼,使用 Expression Blend 來建立的話會非常容易。

在這個任務中,您將會建立一個當用戶成功完成拼圖遊戲時要播放的腳本。這個動畫建立一個這樣的視覺效果:拼圖圖片將沿其中間軸旋轉同時顯示一個圍繞圖片伴有逐漸顯示的祝賀消息的框架。

1. 首先,想頁面中插入一個新的 Resources 區段,如下面程式碼片段 (藍底標示部分) 所示。

XAML

<phone:PhoneApplicationPage 
  x:Class="WindowsPhonePuzzle.PuzzlePage" ...>

  <phone:PhoneApplicationPage.Resources>
  </phone:PhoneApplicationPage.Resources>
  ...
  <Grid x:Name="LayoutRoot" Background="Transparent">
  </Grid>
</navigation:PhoneApplicationPage>

說明: Resources 提供了一個簡單的方法來重覆使用共用定義 (commonly defined) 的物件和值。您可為共用項目建立定義包括控制項範本、樣式、畫刷、色彩和動畫腳本並把它們都儲存在資源字典 (Resource Dictionary) 中。一個資源字典就是物件的鍵值字典,你可以在 XAML 和程式碼中使用該字典。您可以在您應用程式的結構中的不同範圍內定義資源字典,允許您定義頁面級 (page level) 或應用程式級 (application level) 的資源。

在這個任務中,您可以利用頁面的資源來儲存動畫腳本的定義。

2. 接著,在 Resources 區段內部,插入一個當用戶完成拼圖時要播放的動畫腳本來實現過渡目的。為實現這個目的,插入以下 XAML 標記程式碼 (藍底標示部分) 來定義 WinTransition 動畫腳本。

XAML

...
<phone:PhoneApplicationPage.Resources>
  <Storyboard x:Name="WinTransition">
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PreviewImage" Storyboard.TargetProperty="(UIElement.Opacity)">
      <EasingDoubleKeyFrame KeyTime="00:00:00" Value="0.2"/>
      <EasingDoubleKeyFrame KeyTime="00:00:00.7000000" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
      <EasingDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
      <EasingDoubleKeyFrame KeyTime="00:00:00.7000000" Value="-1">
        <EasingDoubleKeyFrame.EasingFunction>
          <CubicEase EasingMode="EaseInOut"/>
        </EasingDoubleKeyFrame.EasingFunction>
      </EasingDoubleKeyFrame>
      <EasingDoubleKeyFrame KeyTime="00:00:01.7000000" Value="1">
        <EasingDoubleKeyFrame.EasingFunction>
          <CubicEase EasingMode="EaseInOut"/>
        </EasingDoubleKeyFrame.EasingFunction>
      </EasingDoubleKeyFrame>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CongratsBorder" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
      <EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
      <EasingDoubleKeyFrame KeyTime="00:00:00.7000000" Value="-1">
        <EasingDoubleKeyFrame.EasingFunction>
          <CubicEase EasingMode="EaseInOut"/>
        </EasingDoubleKeyFrame.EasingFunction>
      </EasingDoubleKeyFrame>
      <EasingDoubleKeyFrame KeyTime="00:00:01.7000000" Value="1">
        <EasingDoubleKeyFrame.EasingFunction>
          <CubicEase EasingMode="EaseInOut"/>
        </EasingDoubleKeyFrame.EasingFunction>
      </EasingDoubleKeyFrame>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CongratsBorder" Storyboard.TargetProperty="(UIElement.Opacity)">
      <EasingDoubleKeyFrame KeyTime="00:00:01.2000000" Value="0"/>
      <EasingDoubleKeyFrame KeyTime="00:00:01.3000000" Value="0"/>
      <EasingDoubleKeyFrame KeyTime="00:00:01.4000000" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
  </Storyboard>

</phone:PhoneApplicationPage.Resources>

**說明:**當用戶贏得這個遊戲時將會播放 WinTransition 腳本。這個動畫包含了完成後的拼圖圖片圍繞其中軸線旋轉,以及圍繞拼圖圖片顯示伴有逐漸顯示 (fade in) 的祝賀資訊的框架。

您可以在 designer 中使用一些工具例如 Expression Blend 來視覺化設計動畫屬性並建立 XAML 標記程式碼來定義一個動畫腳本。想獲得如何利用 Expression Blend 來建立一個動畫的說明,請參閱 Hello Phone 實驗手冊。

3. 最終,當用戶重新啟動遊戲時,為實現重啟操作的過渡時期,插入一個動畫腳本。

XAML

...
<phone:PhoneApplicationPage.Resources>
  <Storyboard x:Name="WinTransition">
    ...
  </Storyboard>
  <Storyboard x:Name="ResetWinTransition">
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CongratsBorder" Storyboard.TargetProperty="(UIElement.Opacity)" Duration="00:00:00.0010000">
      <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PreviewImage" Storyboard.TargetProperty="(UIElement.Opacity)" Duration="00:00:00.0010000">
      <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.20000000298023224"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="GameContainer" Storyboard.TargetProperty="(UIElement.Opacity)">
      <EasingDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
  </Storyboard>
</phone:PhoneApplicationPage.Resources>
...

**說明:**這個 ResetWinTransition 動畫腳本隱藏了祝賀資訊,並恢復拼圖圖片的不透明度到初始值。

4. 在 Solution Explorer,於 PuzzlePage.xaml 上按右鍵,並選擇 View Code ( ) 來打開這個頁面的程式碼隱藏檔。

5. 在 PuzzlePage 類別的建構函數中,定位處理 GameOver 事件的匿名方法,並在方法開始處插入下面程式碼 (藍底標示部分)。

(程式碼片段 – YourFirstWP7App –練習2任務4步驟5 – WinTransition動畫)

C#

public PuzzlePage()
{
  ...
  this.game.GameOver += delegate
  {
    this.WinTransition.Begin();
    this.TapToContinueTextBlock.Opacity = 1;
    this.StatusPanel.Visibility = Visibility.Visible;
    this.TotalMovesTextBlock.Text = this.game.TotalMoves.ToString();
  };
  ...
}

**說明:**當遊戲結束,插入的程式碼播放動畫腳本來提供完整的拼圖圖片和祝賀資訊的視覺效果。

6. 在 PuzzlePage 類別建構函數中,找到 GameStarted 事件的控制碼,添加以下程式碼行 (藍底標示部分)。

(程式碼片段 – YourFirstWP7App –練習2任務4步驟6 – ResetWinTransition動畫)

C#

public PuzzlePage()
{
  ...
  this.game.GameStarted += delegate
  {
    this.ResetWinTransition.Begin();
    this.StatusPanel.Visibility = Visibility.Visible;
    this.TapToContinueTextBlock.Opacity = 0;
    this.TotalMovesTextBlock.Text = this.game.TotalMoves.ToString();
  };
  ...
}

**說明:**這個插入的程式碼將會在遊戲啟動時播放 ResetWinTransition 動畫腳本。這個腳本會重置背景圖片的狀態並隱藏祝賀資訊。


任務 5 – 驗證

1. 要測試最近的更改,按下 F5,再次建置和部署應用程式到模擬器上。

2. 一旦版權頁被顯示,點擊 START 來開始一個新遊戲。

3. 在這一點上,如果你願意,你可以試圖通過拖曳拼圖板上的拼圖碎片來完成整個遊戲;否則,按下遊戲面板上的 SOLVE 按鈕來完成拼圖並結束遊戲。注意當遊戲結束,將會觸發一個動畫:拼圖圖片圍繞中軸線旋轉並在拼圖板上方伴有視覺上逐漸顯示的“CONGRATULATIONS ”文字說明。


圖表 34
完成拼圖 顯示祝賀資訊

4. 當您完成了驗證,回到 Visual Studio 上按下 SHIFT + F5 來結束除錯程序。


練習 3:使用隔離儲存區保存遊戲的狀態

在這個練習中,您將更新這個應用程式來保存其狀態,這樣就可以在下次打開時繼續這個遊戲。要實現這個功能,您需要使用隔離儲存區 (Isolated Storage)。

隔離儲存區允許 Managed 應用程式 (.NET應用程式或 Silverlight 應用程式) 在隔離儲存區中保存和獲取資訊。這個架構與 Silverlight4 中使用的架構非常相似。所有的 I / O 操作被限制在隔離儲存區的範圍內;他們不會直接存取底層作業系統的檔案系統。

使用隔離儲存區會帶來的好處還有應用程式之間的資料保護,配額管理 (quota management) 以及遮障處理 (blinder) 來保證應用程式僅能處理它們自己的資料。

任務 1 – 添加組件引用以及其他 Assets

若要利用隔離儲存區,您需要添加一個提供的輔助檔 (helper file) 和組件引用到專案中。

1. 如果還沒有打開,則啟動用於 Windows Phone 的 Microsoft Visual Studio 2010 Express:Start | All Programs | Microsoft Visual Studio 2010 Express | Microsoft Visual Studio 2010 Express for Windows Phone.

**Visual Studio 2010:**從作業系統的 Start | All Programs | Microsoft Visual Studio 2010 啟動 Visual Studio 2010。

2. 如果您已經完成了前面練習中的所有步驟,你將可以繼續並利用您在之前練習中建立的解決方案;否則,從實驗下的 Source 資料夾中的 Ex2-CreatingThePuzzleView\Begin 處打開 Begin.sln.

3. 加入對 System.Servicemodel.Web 組件的引用:

  • Solution Explorer 中的 References 資料夾上按右鍵。
  • 選擇 Add Reference
  • 選擇 ** .NET標籤**。
  • 從組件清單中選擇 System.Servicemodel.Web 組件。
  • OK。


表 35
為專案添加組件引用

說明:System.Servicemodel.Web 組件中包含了 DataContractJsonSerializer,應用程式在保存物件到隔離儲存區之前,使用 JavaScript Object Notation (JSON) 來序列化物件。

4. 現在,添加一個輔助類別到專案中來處理隔離儲存區:

  • Solution Explorer 中在專案上按右鍵。
  • 指向 Add。
  • 選擇Existing Item。
  • 設置 Browse 值為實驗目錄下的 Source 資料夾中的 Assets。
  • 選擇 IsolatedStorageHelper.cs。
  • 點擊 Add。

說明:IsolatedStorageHelper 類包含把儲存到隔離儲存區和從隔離儲存區中取回的物件序列化的方法。所提供的類別包含好幾個方法的副本 (stubs),您將會在本實驗中來實作它們。

5. 當完成添加引用和輔助類別檔案的操作後,這個已經更新過的解決方案應該類似:


圖表 36
Solution Explorer 展示了新添加的組件


任務 2 – 更新 Puzzle UI

在這個任務中,您更新了遊戲頁面來提供 3 個按鈕 ,允許使用者載入,保存和刪除遊戲的狀態。出於這個目的,您需要更改使用者介面的 XAML 標記來定義所需的元素同時為每個按鈕建立事件處理常式。最後,您需要實現 IsolatedStorageHelper 中的方法,來完成載入,保存和刪除序列化隔離儲存區中的物件 。

1. 在 Solution Explorer 中,按兩下 PuzzlePage.xaml 檔來打開包含此使用者介面的頁面。

2. 切換到 XAML 視窗。按兩下 designer 視窗上右邊框上的 XAML 標籤,來實現更改視窗使其最大化。如果您不方便找到正確的標籤,可以把滑鼠停留在每個標籤上顯示提示資訊,從而找到正確的標籤。

**說明:**如果您的實際工具視窗選擇的是水準分隔視圖,則標籤將會被自動放置到視窗的下邊緣。

3. 在最後一個名為 TapToContinueTextBlockTextBlock 元素中插入以下 XAML 標記程式碼 (藍底標示部分)。

XAML

...
<Grid x:Name="LayoutRoot" Background="Transparent">
  <StackPanel Orientation="Vertical" VerticalAlignment="Top" Grid.Row="1">
    <Border x:Name="CongratsBorder"...>
      ...
    </Border>
    <Border x:Name="border"...>
      ...
    </Border>
    <TextBlock x:Name="TapToContinueTextBlock" HorizontalAlignment="Center" Text="Tap the picture to start the puzzle" TextWrapping="Wrap" Foreground="#FFD0D0D0" FontSize="17.333"/>
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
      <Button x:Name="LoadButton" Content="Load" Margin="10" />
      <Button x:Name="SaveButton" Content="Save" Margin="10" />
      <Button x:Name="ClearStorageButton" Content="Clear" Margin="10" />
    </StackPanel>
  </StackPanel>
</Grid>
...

**說明:**添加的 XAML 標記程式碼將會頁面添加三個按鈕,它們將會載入、保存和清除保存在隔離儲存區中的資料。

4. 切換到 Design 視圖,查看添加完新的按鈕後使用者介面所發生的變化。要做到這點, 在設計工具 designer 視窗右邊緣上按兩下 Design 標籤。


圖表 37
Design 視圖顯示了更新的使用者介面

5. 現在處理每個按鈕定義事件處理常式:在 designer 上點擊標記為 “Load” 的按鈕來選擇它,並按 F4 來打開它的 Properties 視窗。

6. 在 Properties 視窗中:

  • 點擊 Events 標籤來顯示可用事件清單視窗。
  • 在列表中 Click 事件,然後在事件旁的文字方塊中輸入 LoadButton_Click

點按 Enter 來生成一個以此命名的事件處理常式,同時打開程式碼隱藏檔來顯示 Visual Studio 產生的方法宣告。

7. 插入以下程式碼 (藍底標示部分) 到 LoadButton_Click 事件處理常式內:

(程式碼片段 – YourFirstWP7App –練習3任務2步驟7 – LoadButton_Click事件控制碼)

C#

private void LoadButton_Click(object sender, RoutedEventArgs e)
{
  var gameState = IsolatedStorageHelper.GetObject<PuzzleState>("PuzzleState");
  if (gameState == null)
  {
    MessageBox.Show("Sorry, no game state found.", "Oops!", MessageBoxButton.OK);
  }
  else
  {
    // set game state
    this.game.SetState(gameState);
  }
}

8. 然後,在程式碼編輯視窗上右鍵點擊並選擇 View Designer,切換回到 Design 視圖。點擊標記為 “Save” 的按鈕,選中它並按 F4 來打開其 Properties 視窗。

9. 在 Properties  視窗上,按 Events  標籤,並找到 Click 事件,在事件旁的文字方塊中輸入 SaveButton_Click。點按 Enter 來生成一個以此命名的事件控制碼,同時打開程式碼隱藏檔來顯示 Visual Studio 產生的方法宣告。

10. 插入以下程式碼 (藍底標示部分) 到 SaveButton_Click 事件處理常式內:

(程式碼片段 – YourFirstWP7App –練習3任務2步驟10 – SaveButton_Click事件控制碼)

C#

private void SaveButton_Click(object sender, RoutedEventArgs e)
{
  // save game state
  PuzzleState gameState = this.game.GetState();
  IsolatedStorageHelper.SaveObject("PuzzleState", gameState);
}

11. 然後再一次,在程式碼編輯視窗上右鍵點擊並選擇 View Designer,切換回到 Design 視圖。點擊標記為 “Clear” 的按鈕,選中它並按 F4 來打開其 Properties 視窗。

12. 在 Properties  視窗上,按 Events  標籤,並找到 Click 事件,在事件旁的文字方塊中輸入 ClearStorageButton_Click。點按 Enter 來生成一個以此命名的事件處理常式,同時打開程式碼隱藏檔來顯示 Visual Studio 產生的方法宣告。

13. 插入以下程式碼 (藍底標示部分) 到 ClearStorageButton_Click 事件處理常式內:

(程式碼片段 – YourFirstWP7App –練習3任務2步驟13 – ClearStorageButton_Click事件控制碼)

C#

private void ClearStorageButton_Click(object sender, RoutedEventArgs e)
{
  // remove state and image
  IsolatedStorageHelper.DeleteObject("PuzzleState");
}

14. 現在,在 Solution Explorer 中,按兩下 IsolatedStorageHelper.cs 文件打開它。

15. 找到 GetObject <T> 方法並用下面程式碼 (藍底標示部分) 替換其方法內容:

(程式碼片段 – YourFirstWP7App –練習3任務2步驟15 – GetObject方法)

C#

public static T GetObject<T>(string key)
{
  if (IsolatedStorageSettings.ApplicationSettings.Contains(key))
  {
    string serializedObject = IsolatedStorageSettings.ApplicationSettings[key].ToString();
    return Deserialize<T>(serializedObject);
  }

  return default(T);
}

**說明:**GetObject <T>方法會以給定的鍵值,由隔離儲存區取出物件,它擁有 IsolatedStorageSettings 類別指示將物件以鍵值對字典方式儲存到隔離儲存區的優點。物件會使用在 System.ServiceModel.Web 組件提供的 DataContractJsonSerializer 類別序列化後的格式保存,它會將物件序列化成 JSON 格式,並且負責反序列化回到物件。

16. 接著,插入下面程式碼 (藍底標示部分) 到 **SaveObject <T>**方法內容:

(程式碼片段 – YourFirstWP7App –練習3任務2步驟16 – SaveObject方法)

C#

public static void SaveObject<T>(string key, T objectToSave)
{
    string serializedObject = Serialize(objectToSave);
    IsolatedStorageSettings.ApplicationSettings[key] = serializedObject;
}

說明:SaveObject <T> 方法儲存一個賦有鍵值的物件到隔離儲存區中,這個鍵值被用來從儲存中取回物件。

17. 最後,利用下面程式碼 (藍底標示部分) 實作 DeleteObject <T> 方法 :

(程式碼片段 – YourFirstWP7App –練習3任務2步驟17 – DeleteObject方法)

C#

public static void DeleteObject(string key)
{
  IsolatedStorageSettings.ApplicationSettings.Remove(key);
}

說明:DeleteObject 方法根據給定的物件鍵值從隔離儲存區中移除它。


任務 3 – 驗證

在這個任務中,您將會在 Windows Phone Emulator 上建置與執行這個完成的應用程式。測試一個隔離儲存區的使用,您需要啟動一個新的遊戲並移動拼圖碎片。然後,保存遊戲狀態並關閉它。接著,您不止一次再次啟動應用程式,來恢復所保存的狀態,使得遊戲恢復到與關閉應用程式之前的狀況一致。

1. 要測試最後的更改,按 F5 在模擬器上再次建置和部署應用程式。等待版權頁的出現然後點擊 START 來開始一個新的遊戲。

2. 在遊戲頁面,點按 Load 試圖恢復到任何一個保存的狀態。注意,可想而知地,將會顯示一個錯誤頁面顯示沒有程式運行。點擊 OK 來關閉錯誤消息。


圖表 38
當沒有保存任何先前的狀態,載入操作將會失敗

3. 拖曳拼圖板上的一些拼圖碎片。

最好是您能排列拼圖碎片到一種樣式,使您能很容易記住一個或更多碎片。這樣做將會很容易來判斷您隨後重新載入遊戲後,您獲得了相同的拼圖樣式。

4. 點擊 Save 來保存當前遊戲的狀態。

5. 現在,繼續移動碎片直到你有一個完全不同的排列,但是不要點按 Save。

6. 再次按下 Load 來恢復以前保存的狀態。注意這次,拼圖板上的拼圖碎片位置發生了改變,恢復到了之前您保存遊戲狀態時拼圖板相同的樣式。

7. 在模擬器視窗上點擊 Back 按鈕 ( ) 直到切換到包含著版權頁的主頁面。接著,再次點擊 Back 按鈕來退出應用程式並顯示 Quick Launch 功能表。

8. 現在,從 Quick Launch 功能表重新開機應用程式。要實現這一點,點按箭頭按鈕到 “All Applications.


圖表 39
在 Quick Launch 選單瀏覽 “All Applications”

9. 在安裝程式清單中,點擊 Windows Phone Puzzle 並再次啟動應用程式。


圖表 40
從 Quick Launch 功能表上啟動應用程式

**說明:**當您退出應用程式,Visual Studio 會卸離 (detach) 除錯器。在模擬器中仍然保存著應用程式影像,您可以隨時重新啟動它。注意,無論如何,當您這樣做時應用程式不再執行在除錯器上。

10. 點擊 START 來開始一個新的遊戲,然後在遊戲頁面中點擊 Load 來恢復之前保存的狀態。驗證拼圖板的樣式和之前您退出程式前保存的狀況是一致的,這就證明即使您退出了應用程式,隔離儲存區也被保留了。

11. 點擊 Clear,刪除隔離儲存區中保存的狀態。

12. 現在,再次點擊 Load,注意您這次接收到了您在開始時看到的相同錯誤資訊,表明您已經成功清除了保存的狀態。


總結

這個實驗帶領您完成了利用針對 Windows Phone 的 Microsoft Visual Studio 2010 Express  來開發一個用於 Windows Phone 的 Silverlight 應用程式。在這個實驗的過程中,您建立了一個使用 Extensible Application Markup Language (XAML) 開發的使用者介面並看到了如何編寫應用程式邏輯以及添加程式碼以回應輸入事件。您瞭解到 Windows Phone 應用程式如何處理多點觸控的輸入來操控使用者介面上的元素,以及如何運用動畫效果來改善用戶體驗。最後,您還探討了利用隔離儲存區來保存應用程式狀態的方法。