原文 Windows Phone 8初学者开发—第21部分:永久保存Wav音频文件
第21部分:永久保存Wav音频文件
系列地址:http://channel9.msdn.com/Series/Windows-Phone-8-Development-for-Absolute-Beginners
源代码: http://aka.ms/absbeginnerdevwp8
PDF版本: http://aka.ms/absbeginnerdevwp8pdf
现在我们可以录制声音并将它保存到应用程序独立存储的临时文件中。接下来我们需要提示用户输入新的自定义声音的显示名称来允许用户永久保存声音。
本课的计划:
- 向"save"应用栏按钮添加事件处理程序方法
- 我们将管理应用栏的状态,它应该仅在临时声音文件被创建并准备好被永久保存时可见。
- 我们将再次使用Coding4Fun工具包,这次用于显示输入对话框(InputDialog)以获取新的自定义声音音频文件的名称。
- 我们将把CustomSounds数据序列化成一个JSON文件
- 我们将修改数据模型以同时加载自定义声音JSON文件并创建自定义声音数据模型的实例
1. 向"save"按钮添加事件处理程序方法并管理应用栏的状态
前面我们通过启用BuildLocalizedApplicationBar()为RecordAudio.xaml页面创建了应用栏。所以我们需要做的就是激活它:
- 在43行,我使用在整个系列中利用的技术向Click方法添加了一个事件处理程序
- 在46行,我立即隐藏应用栏。我们仅在有声音需要保存时(在用户录制自定义声音后)才显示它
接着,我们将在用户停止录制后启用应用栏。在RecordAudioUnchecked()方法中,我们将IsVisible属性设置为true(见以下67行):
2. 使用Coding4Fun工具包显示输入对话框以获取新的自定义声音音频文件名称
在上面的步骤中我们为SaveRecordingClick()方法添加了一个方法存根
我将替换抛出异常作为提醒的那行代码并编写以下代码(见51行):
因为InputPrompt(输入提示)来自与我们目前使用的其它类不同的命名空间,我们需要添加一个using语句(使用悬停于蓝色虚线的方法以显示一个上下文菜单)。
接着我们将配置并显示InputPrompt:
- 我们在这里设置出现在InputPrompt中的标题和消息
- 我们向Completed事件附加一个事件处理程序方法(并使用我在之前演示过的技术生成方法的存根)。我将在下一步继续处理它。
- 一旦配置完成,我将显示对话框
当用户为新的自定义声音输入名称并单击勾选按钮时,FileNameCompleted()事件处理程序将被触发。
我们将通过检查结果确保用户正确退出InputPrompt。我们将检查作为输入参数发送给事件处理程序方法的 PopUpResult。如果结果是"OK",则我们就可以执行必要的逻辑来将临时文件保存为新的"永久的"声音。请查看我添加的代码以及代码的注释,这 些注释提供了一个我希望执行的"后续步骤"的一个大纲:
- 如果用户正确输入一个新的名称并单击勾选按钮退出输入对话框,那么我们将执行保存自定义声音所需的任务并使其出现在声音面板的自定义声音视图中
- 最后,我们将导航回MainPage.xaml
在注释1和2之间是一个需要做些什么以便正常工作的概述。在我们尝试实施这些想法前,让我们通过运行应用程序来确保到目前为止流程的运作与我们的期望相一致。
我使用切换按钮录制自定义声音。当我停止录制时,我将会看到应用栏的出现:
当我单击磁盘图标保存自定义声音时,将显示输入对话框:
并且当我输入声音名称并单击勾选图标时,对话框将消失并把我带回MainPage.xaml。很好!
现在让我们处理困难的部分,执行代码注释中列出的任务。
3.将声音文件保存到永久的独立存储区域,将CustomSounds数据序列化到JSON文件
至此我们录制了自定义声音并将它作为临时文件存储,并且我们刚刚为声音收集了一个友好的显示名称。我们需要完成两项基本的任务:
- 首先,我们需要向数据模型添加自定义声音。如果新的自定义声音不被添加到数据模型,那么我们永远无法在MainPage.xaml上的自定义声音视图呈现它。所以,我们将创建SoundData类的新的实例并正确填写FilePath和Title属性。
- 接着,我们准备将文件从临时位置移动到称为/customAudio/的永久子文件夹。这纯粹是为了在同一位置保存所有的自定义声音文件。
所以我向FileNameCompleted()方法添加了以下代码:
- 我创建一个新的SoundData实例并填写Title和FilePath属性。请注意我们将给自定义声音一个新的名称,但是文件的内容保持不变。
- 就像原来录制自定义声音那样,我们获得一个专门针对应用程序的独立存储区域的引用。我们使用using语句以正确释放非托管资源(例如手机的存 储)。代码第一次执行时将会创建存储自定义音频文件的特殊文件夹(76行)。最后,我们将临时文件移动至新的永久存储区域,并一次性给它一个新的名称。
- 接着,我们向CustomSounds.Items集合添加新的SoundData类的实例。这时我们应该能够返回MainPage.xaml,并在自定义声音列表看到新的自定义声音。
然而,当我们关闭应用程序并且它被完全从手机内存中删除,将会发生什么?届时CustomSounds.Items集合 将从内存中删除,并且下一次应用程序运行时,应用程序将无法获得我们的自定义声音。我们需要一种方法存储自定义的声音数据,这样我们就可以在下次用户运行 应用程序时将其加载到我们的数据模型中。
4.将CustomSounds声音组序列化到JSON,并从JSON反序列化到CustomSounds声音组
为此我们需要将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等内容。我们已经基本完成了应用程序,我们将进行最后的加工以使它更有趣。