現在我們可以錄制聲音并将它儲存到應用程式獨立存儲的臨時檔案中。接下來我們需要提示使用者輸入新的自定義聲音的顯示名稱來允許使用者永久儲存聲音。
本課的計劃:
向"save"應用欄按鈕添加事件處理程式方法
我們将管理應用欄的狀态,它應該僅在臨時聲音檔案被建立并準備好被永久儲存時可見。
我們将再次使用Coding4Fun工具包,這次用于顯示輸入對話框(InputDialog)以擷取新的自定義聲音音頻檔案的名稱。
我們将把CustomSounds資料序列化成一個JSON檔案
我們将修改資料模型以同時加載自定義聲音JSON檔案并建立自定義聲音資料模型的執行個體
前面我們通過啟用BuildLocalizedApplicationBar()為RecordAudio.xaml頁面建立了應用欄。是以我們需要做的就是激活它:

在43行,我使用在整個系列中利用的技術向Click方法添加了一個事件處理程式
在46行,我立即隐藏應用欄。我們僅在有聲音需要儲存時(在使用者錄制自定義聲音後)才顯示它
接着,我們将在使用者停止錄制後啟用應用欄。在RecordAudioUnchecked()方法中,我們将IsVisible屬性設定為true(見以下67行):
在上面的步驟中我們為SaveRecordingClick()方法添加了一個方法存根
我将替換抛出異常作為提醒的那行代碼并編寫以下代碼(見51行):
因為InputPrompt(輸入提示)來自與我們目前使用的其它類不同的命名空間,我們需要添加一個using語句(使用懸停于藍色虛線的方法以顯示一個上下文菜單)。
接着我們将配置并顯示InputPrompt:
我們在這裡設定出現在InputPrompt中的标題和消息
我們向Completed事件附加一個事件處理程式方法(并使用我在之前示範過的技術生成方法的存根)。我将在下一步繼續處理它。
一旦配置完成,我将顯示對話框
當使用者為新的自定義聲音輸入名稱并單擊勾選按鈕時,FileNameCompleted()事件處理程式将被觸發。
我們将通過檢查結果確定使用者正确退出InputPrompt。我們将檢查作為輸入參數發送給事件處理程式方法的
PopUpResult。如果結果是"OK",則我們就可以執行必要的邏輯來将臨時檔案儲存為新的"永久的"聲音。請檢視我添加的代碼以及代碼的注釋,這
些注釋提供了一個我希望執行的"後續步驟"的一個大綱:
如果使用者正确輸入一個新的名稱并單擊勾選按鈕退出輸入對話框,那麼我們将執行儲存自定義聲音所需的任務并使其出現在聲音面闆的自定義聲音視圖中
最後,我們将導航回MainPage.xaml
在注釋1和2之間是一個需要做些什麼以便正常工作的概述。在我們嘗試實施這些想法前,讓我們通過運作應用程式來確定到目前為止流程的運作與我們的期望相一緻。
我使用切換按鈕錄制自定義聲音。當我停止錄制時,我将會看到應用欄的出現:
當我單擊磁盤圖示儲存自定義聲音時,将顯示輸入對話框:
并且當我輸入聲音名稱并單擊勾選圖示時,對話框将消失并把我帶回MainPage.xaml。很好!
現在讓我們處理困難的部分,執行代碼注釋中列出的任務。
至此我們錄制了自定義聲音并将它作為臨時檔案存儲,并且我們剛剛為聲音收集了一個友好的顯示名稱。我們需要完成兩項基本的任務:
首先,我們需要向資料模型添加自定義聲音。如果新的自定義聲音不被添加到資料模型,那麼我們永遠無法在MainPage.xaml上的自定義聲音視圖呈現它。是以,我們将建立SoundData類的新的執行個體并正确填寫FilePath和Title屬性。
接着,我們準備将檔案從臨時位置移動到稱為/customAudio/的永久子檔案夾。這純粹是為了在同一位置儲存所有的自定義聲音檔案。
是以我向FileNameCompleted()方法添加了以下代碼:
我建立一個新的SoundData執行個體并填寫Title和FilePath屬性。請注意我們将給自定義聲音一個新的名稱,但是檔案的内容保持不變。
就像原來錄制自定義聲音那樣,我們獲得一個專門針對應用程式的獨立存儲區域的引用。我們使用using語句以正确釋放非托管資源(例如手機的存
儲)。代碼第一次執行時将會建立存儲自定義音頻檔案的特殊檔案夾(76行)。最後,我們将臨時檔案移動至新的永久存儲區域,并一次性給它一個新的名稱。
接着,我們向CustomSounds.Items集合添加新的SoundData類的執行個體。這時我們應該能夠傳回MainPage.xaml,并在自定義聲音清單看到新的自定義聲音。
然而,當我們關閉應用程式并且它被完全從手機記憶體中删除,将會發生什麼?屆時CustomSounds.Items集合
将從記憶體中删除,并且下一次應用程式運作時,應用程式将無法獲得我們的自定義聲音。我們需要一種方法存儲自定義的聲音資料,這樣我們就可以在下次使用者運作
應用程式時将其加載到我們的資料模型中。
為此我們需要将CustomSounds.Items集合序列化到一種資料格式。有許多資料格式可供選擇,但是我們将選
擇一種非常流行,輕量級并易于使用的格式——JSON。它是JavaScript Object
Notion(JavaScript對象表示法)的縮寫。它使我們友善地用JavaScript對象表示集合。如果我們利用名為Json.NET的第三方
開源庫,我們甚至不用考慮資料的格式,大部分複雜性将被簡單的方法調用隐藏。
首先,我們将打開NuGet程式包管理器(使用我在前面示範過的技術,右鍵單擊引用檔案夾并選擇管理NuGet程式包選項)。
搜尋Json,排在頂部的應該就是Json.NET。
單擊Json.NET程式包旁的安裝按鈕。需要花一些事件将程式包安裝到您的項目中。
單擊關閉按鈕。
為驗證Json.NET是否安裝成功,打開SoundBoard項目的引用檔案夾并驗證Newtonsoft.Json将出現在那裡:
回到FileNameCompleted()方法,下一步是将CustomSounds.Items轉換為Json,讓後将它存儲到磁盤。
我們将使用Newtonsoft.Json.JsonConvert類執行轉換。您需要添加适當的using語句以使用JsonConvert類:
現在我們準備實作CustomSounds Json檔案到磁盤的存儲。
我們使用JsonConvert.SerializeObject()方法将CustomSounds對象(以及它的子對象)序列化到Json
我們将使用獨立存儲中稱為IsolatedStorageSettings的特定區域儲存對象資料。這是一個簡單的儲存應用程式設定的方法。您可
以使用名稱/值對的模式為應用程式儲存任意設定。是以,在本例中我們将建立一個鍵并提供相應的值,值部分顯然是資料,即上一行代碼中被序列化的Json數
據。鍵部分是一個我們将在SoundModel類的定義中作為常量屬性建立的字元串。我們稍後需要利用該鍵從
IsolatedStorageSettings檢索回Json資料。
我們将調用Save()方法以實際儲存新的應用程式設定,即我們在上一行代碼中建立的新的名稱/值對。
在忘記之前,讓我們在SoundModel.cs檔案中定義CustomSoundKey,我将添加以下代碼行(見19行):
如您所見,這是一個常量字元串值。我們希望它是常數,因為它不會改變。它隻是在獨立存儲中找回正确的應用設定(ApplicationSetting)的一個唯一的字元串。
接着,我們将在執行個體化所有其它SoundGroup對象的同時加載自定義聲音到記憶體中,在SoundModel.cs檔案的LoadData()方法中:
在上述28行,我們将調用一個輔助方法LoadCustomSounds()以填充SoundModel類的CustomSounds屬性。使用我在之前示範過的技術為新的方法生成一個存根。
在LoadCustomSounds()方法中,我們将嘗試從IsolatedStorageSettings中檢索包含序列化自定義聲音的Json:
我們執行IsolatedStorageSettings.ApplicationSettings上的TryGetValue(),如果在
IsolatedStorage中存在CustomSoundKey,那麼它應該在輸出參數"dataFromAppSettings"中傳回相應的值
(即我們在之前存儲的Json)。如果沒有,else代碼塊将建立一個新的(空的)SoundGroup的執行個體。
既然我們對資料進行了序列化,我們希望将資料反序列化回SoundGroup和SoundData對象的執行個體。我們調用泛型方法
DeserializeObject<T>,向它提供我們希望反序列化成的類型(即SoundGroup)并傳遞從
IsolatedStorageSettings檢索的資料。
假設執行TryGetValue失敗,這意味着沒有自定義聲音被建立(或檢索資料出現問題)。在任何情況下,傳回一個空的SoundGroup。
現在讓我們動手測試應用程式。我将錄制一個聲音并嘗試用名稱"another test"儲存該聲音。
一切看起來都很好,但是當我儲存新的自定義聲音後傳回MainPage.xaml并嘗試播放它時會發現,它無法播放!這是因為我們需要修改MainPage.xaml上的播放代碼以從新的檔案夾加載自定義聲音。目前它僅從/Assets檔案夾加載。
我們的目标是将MediaElement的Source屬性設定為正确的聲音檔案的位置,這些聲音檔案與使用者點選的磁貼相關。我們将在兩個位置查找,或者是/Assets檔案夾,或者是獨立存儲區域。
在MainPage.xaml.cs檔案的LongListSelector事件處理程式方法中,我将添加以下内容:
這裡我嘗試檢視使用者選擇的磁貼是否是自定義聲音。如果我們不能在Assets\檔案夾定位與磁貼相關的檔案,那麼将在應用程式獨立存儲區域中的/customSounds/子檔案夾中查找。
對于上述檢查,我們需要通路手機的檔案系統,是以我們将使用System.IO.File對象。以上截圖顯示了如何在代碼檔案頂部添加合适的using語句以包含System.IO。
此外,我們希望包含System.IO.IsolatedStorage引用,因為接下來我們将使用該命名空間中的類。
回到LongListSelector的SelectionChanged()事件處理程式方法,如果
SoundData對象的FilePath所指的檔案不能在預設位置被找到,則File.Exists()将傳回false。否則,它将位于
/Assets檔案夾。是以基于以上分析我編寫了以下代碼:
與之前一樣,我在此處使用SoundData的FilePath屬性設定MediaElement的Sound屬性。
在本例中,檔案不在/Assets檔案夾中,是以我們将在獨立存儲中搜尋。我在這裡建立了一個IsolatedStorage引用。請注意using語句,通過using語句我們可以在結束時正确地釋放該資源。
我們在這裡使用了一種新的技術來通路檔案。我們以流的方式打開自定義聲音,或者更具體地講是IsolatedStorageFileStream。
我們在這裡将來自獨立存儲的包含自定義聲音的流作為參數傳遞給SetSource。
這次我們運作應用程式,錄制并儲存聲音,然後傳回“我的”自定義聲音類别,每一個儲存的聲音應該都可以正确播放了!
綜上所述,本課的重點是使用Newtonsoft
Json序列化和反序列化對象到JSON,它是一個提升手機開發的非常有價值的技能。我們還學習了如何處理IsolatedStorage,特别是用
IsolatedStorageSettings來存儲名稱/值對。我們使用System.IO.File類來檢查檔案系統,并學習了如何處理流。我們還
使用了Coding4Fun工具包中的InputPrompt等内容。我們已經基本完成了應用程式,我們将進行最後的加工以使它更有趣。