随笔-193  评论-1124  文章-1  trackbacks-4

《101 Windows Phone 7 Apps》读书笔记-BABY MILESTONES

课程内容

Ø图片的读写

Ø序列化

Ø双向数据绑定


    Baby Milestones将婴儿从出生到2岁之间的发展关键里程碑通知给父母。该应用程序使得父母能够跟踪发展里程碑,并确保他们的宝宝正常成长。它会把婴儿每个阶段可以完成的技能按照月份的列表显示出来,使得父母能够记录宝宝获取该技能的日期。该应用程序的主页面显示宝宝当前每个月的成长数据榜。

    该应用的额外特色正是将其安排在本章讲述的主要原因。它展示了如何在隔离存储空间中存储、获取并显示图片。该应用中每个月的列表(从1到24)支持自定义图片作为页面背景,其主要思想是父母能够在合适的时间给宝宝拍摄照片,为每个列表提供一些怀旧的内容。

 

The Main Page

    主页面如图23.1所示,它包含了一个list box控件,通过它可以链接到24个月份的列表。List中的每个label伴随一个progress bar,它展示当前每个月的发展程度。完成的月份以照片的前景色显示,而未完成的月份则以照片的强调色显示。

image

图23.1 进度条将简单的list box变成了一个有用的面板视图

注意:

➔ 该应用程序利用了以下两个Settings.cs中定义的设置,Data.Ages展示了24个包含一系列技能的阶段列表。

➔ 在该页面的XAML代码中,数据模板中的进度条直接与每个Age实例的PercentComplete属性进行绑定。但是,为了使每个text block控件有合适的前景色,这里使用了自定义值转换器。本应用程序使用了3个值转换器,在下一节中详述。

➔ 在背后代码中,MainPage_Loaded方法确保选择视图中显示最近的阶段,特别是一旦宝宝超过了9各月,让用户每次都通过滚动条来查看会显让他们觉得很懊恼。这通过BeginInvoke调用来完成,因为在设置数据内容以后立刻操作list box的滚动条,这样可能不行。我们需要在这种方法操作list box之前完成数据绑定。

➔ 在Windows Phone应用程序中,list box最常用的SelectionChanged事件(只有在选定的内容改变以后才会触发,而非点击操作就可以)在这里是不希望出现的。因此,这里使用ListBox_SelectionChanged方法清除刚刚选择的内容,在同一个记录上进行连续点击也是一样。

Age and Skill

➔ Age 和 Skill这两个类都实现了INotifyPropertyChanged接口,在属性改变时,会触发PropertyChanged事件,如同数据绑定中的数据源。这就使得记录可以显示在主页面上,并且使得details页面(下一节讲述)保持更新,而不用手动进行操作。

➔ 由于Age类中的PercentComplete属性是以Skill列表的每个Date字段为基础的(null意味着未完成,而存在任何日期就表明已经完成),所以,在合适的时间为PercentComplete来触发PropertyChanged事件就显得比较合适。Age类本来可以为每个Skill实例订阅PropertyChanged事件,并且在日期发生改变时,为PercentComplete来触发事件。相反,Age类只需要使用者在相关的日期改变时,调用RefreshPercentComplete就可以了。

➔ Skill类具有一个显式默认构造函数,因为它需要为隔离存储空间进行序列化。一般情况下,C#编译器会生成隐式默认构造函数。但是,在定义非默认的构造函数时,我们必须显式地定义一个默认构造函数(如果需要的话)。

 

Serialization and Isolated Storage Application Settings

    放置于IsolatedStorageSettings.ApplicationSettings中的每个对象(或者是分配给本书中使用的Settings类的实例)-包括它所有成员的transitive closure-必须要序列化。正如前一章所述,该字典下的内容在ApplicationSettings文件中被序列化为XML。如果存在不可序列化的数据,那么字典中的所有数据将都无法存储。这种错误可能发生于无形,除非我们在调式器中捕获未处理的异常。

    大多数情况下,满足这个需求并不需要额外的工作。这本书中至今没有一个应用需要做特殊的处理来确保它们的设置是可序列化的,包括所有的基本数据类型(string, numeric values, DateTime等等),包括使用了这些基本数据类型的List,以及使用这些数据类型的类,它们都是可序列化的。

    但有的时候,我们需要用自己的方式确保存储的数据是用可序列化的数据类型来描述的。我们可以简单地加入显式默认构造函数来实现,否则的话,我们可能需要花费更多的时间来改变数据类型或者对其进行自定义属性(比如DataMember和IgnoreDataMember,它们使得我们可以自定义类的序列化),我们可以使用IgnoreDataMember属性对其进行标记,从而对其进行排除。

避免存储相同对象的多个引用!

    对于隔离存储空间应用设置字典中的相同对象,虽然我们可以存储它的多个引用,但是在应用程序下一次运行时,这些引用不会指向同一个实例。那是因为当每个应用被序列化的时候,他的数据被存储为独立的备份。在反序列化时,每个数据的备份变成了不同对象的实例。

   这个正是Baby Milestones使用CurrentAgeIndex设置、而不使用存储Age实例引用设置的原因。在序列化与反序列化后,滚动list box的逻辑再也不起任何作用了,因为Age实例已经不在list box之中。

    我们可以通过对System.Runtime中的一些自定义属性进行标记的方法,在序列化和反序列化中加入用户自定义逻辑。Serialization命名空间:OnSerializing, OnSerialized, OnDeserializing, and OnDeserialized。为了使得我们标记的方法在合适的时间调用,它们必须是public类型的(或者包含一个合适的InternalsVisibleTo属性),并且具有一个StreamingContext参数。

 

The Details Page

    Details页面如图23.2所示,它在用户点击主页面上的一个age时出现。该页面显示与age相关的技能列表,点击它能够记录获取技能的日期。点击以后,会弹出一个初始化为当天的date picker,如图23.3所示。

image

图23.2 显示第一个月列表的Details页面

image

图23.3 点击第一条记录以后的Details页面

注意

➔ 每条记录中date picker的可见性和text block是基于Skill实例中的Date属性值。这是通过两个值转换器来完成的。

➔ Date picker的值使用双向数据绑定,这对于那些用户控制属性值的方式非常有用。Skill类实例中Date属性的改变不仅自动在date picker中显示出来,而且用户通过UI对date picker作的改变也会自动回馈给Date属性。

➔该列表使用了自定义的IsolatedStorageHelper类来进行图片文件的加载、保存和删除。如图23.4所示,图片由photo chooser来选择,它将选择的图片以数据流的方式返回。

image

图23.4 Photo chooser支持从媒体库中选择图片或者通过摄像头来拍摄新的图片

IsolatedStorageHelper的注意点

➔ DeleteFile方法与前一章中删除文件的代码相同,SaveFile方法并不指定图片,而是将输入的二进制流存储为一个新的文件流。与图片相关的部分在LoadFile中,它调用PictureDecoder.DecodeJpeg(在Microsoft.Phone命名空间中)将流转换为ImageSource,从而可以将其设置为Image或ImageBrush的源。

➔ DecodeJpeg方法的速度相当慢,并且它必须在UI线程中调用,所以,这个类会缓存所有它创建的ImageSource,使得下次其文件名被传递给LoadFile时,能够快速返回(相同的ImageSource实例可以被多个UI元素共享,所以复用它并不会带来危险)。

    除了PictureDecoder.DecodeJpeg,可以考虑使用WriteableBitmap.LoadJpeg。后者可以在后台线程中调用,避免在解码一个大的图片时所带来的响应迟滞。WriteableBitmap会在第42章的“Jigsaw Puzzle”中进行介绍。

    LoadFile可以使用一个替代的方法来使用隔离存储空间中的图片构造一个ImageSource。它可以用默认的构造方法来构造BitmapImage,然后调用SetSource方法接收一个IsolatedStorageFileStream实例的流。

    如果我们的应用程序允许从摄像头中保存图片,那么就让用户把它保存到媒体库中,这是一个不错的主意。这样一来,即使应用程序卸载了,拍摄的图片仍旧保留在设备中。而且,一旦图片进入媒体库,用户就可以将其同步到电脑或者使用多种方法来共享(比如上传到Facebook或者SkyDrive)。我们可以简单得调用MediaLibrary.SavePicture来实现。

    重载的DecodeJpeg具有maxPixelWidth与maxPixelHeight参数,他们可以根据性能需求来进行图片的剪裁。但是,当JPEG类型图片的宽度大于高度时,DecodeJpeg会将这两个参数混淆。它会使用maxPixelWidth限制高度,使用maxPixelHeight限制宽度。

 

posted on 2012-08-12 07:19 施炯 阅读(...) 评论(...) 编辑 收藏