《101 Windows Phone 7 Apps》读书笔记-BOOK READER
课程内容
本章的Book Reader应用程序为Jane Austen的经典小说《傲慢与偏见》提供一种专门为手机进行优化的阅读体验。书的字体来自Gutenberg项目(www.gutenberg.org),由于其版权已经过期,因此它为美国人民提供了免费的电子书。如果你打算发布一个包含Gutenberg项目的应用程序,确认你已经理解了Gutenberg项目的许可证。详见www.gutenberg.org。
为了达到最好的阅读体验,本应用程序允许用户自定义前景色、背景色、字体大小,甚至是字体集。Book Reader提供了简便的页面导航,允许用户跳转到任何章节或者任何页码。刚开始可能不那么明显,但是实现本应用程序的最大挑战是编页,即在字体设置的基础上为整本书的内容分页。当然,我们可以将整本书的内容放置在具有滚动条页面中,但这并不能够带来好的用户体验。另外,由于UI元素的大小限制,使用其他的方法也未必可行。因此,本应用程序一次只显示一个页面。用户可以通过点击屏幕来翻页,或者点击应用程序栏上的按钮来回退页面。
The Main Page
图25.1显示了应用程序栏展开后的主页面,应用程序栏上具有回退一页按钮、跳转到任何章节或者页码的按钮和改变设置的按钮。应用程序栏区域同时也显示了当前页码和总页码(这是基于当前字体设置情况的)。
图 25.1 主页面图,默认使用类似Amazon Kindle的颜色模式,专门为阅读提供足够的对比度。
注意:
➔ 本应用程序的分页机制由PaginatedDocument用户控件来完成,在本章的最后详述。
➔ 命名为Footer的list box控件出现在应用程序栏中,因为它被放置在该区域的下方,而且应用程序栏的不透明度设置为0。
➔ 如图25.2所示的充满章节的list box,使用了一种非常重要但很难发现的方法,使得list box中的条目按照其宽度进行拉伸,填满该区域。这就可以在不给定宽度的条件下,使得每个条目中的元素(如页码)达到右对齐。
图25.2 使用HorizontalContentAlignment进行拉伸的list box,所以在不给定条目宽度的条件下,使得页码达到右对齐。
➔ 书本是以文本文件的形式包含进来的,其Build Action设置为Content,就和前一章中的数据库文件一样。文件的名称为1342.txt,与Project Gutenberg网站上下载来的文档一致。
➔ 本应用程序使用了如下的设置:读者当前的页码被存储为字符索引,即为包含了整本书的内容的页面的第一个字符建立索引。这是因为如果字体设置不同,那么与书本位置相关的页码也会不同。有了这个方法以后,用户在书本中的真正位置就被保存下来了。
➔ 添加到章节列表中的键值对是一种方便使用的类型,因为它包含了两个独立的字符串属性,数据模板可以将其绑定。其中,“Key”是左对齐的章节标题,“value”是右对齐的页码。
The Settings Page
Book Reader的设置页面几乎与Notepad应用中的设置页面相同。其不同之处在于顶端的font picker,如图25.3所示。该font picker由Silverlight for Windows Phone Toolkit中的list picker控件创建而来。
图25.3 font picker在WYSIWYG picker中显示的10种字体
这里,List picker基本上就是一个combo box。它开始的时候看上去像text box,但是被点击的时候,它允许用户从列表中选择一个值。数据模板同时绑定每个text block的FontFamily和Text属性,在列表中显示每个字符串。List picker支持两种不同的列表展示方式:内联模式和全模式。如图25.3所示,内联模式中,该控件通过流畅的动画来对记录进行展开和合拢。如图25.4所示,在全模式中,该控件显示全屏的记录列表。
图25.4 配置为全模式的Book Reader中font picker
当我尝试在Windows Phone 应用程序中使用ComboBox控件时,为什么显得很奇怪?
ComboBox是一个核心的Silverlight控件,它频繁地使用在网页中,但是,它没有为Windows Phone提供合适的设计风格。所以它一般不会被使用(该控件应该移除来避免困惑)。如果你想使用combo box的话,就用list picker来替代吧。
如果记录的数量少于5个的话,List picker默认使用内联模式;不然的话,它会使用全模式。这与Windows Phone设计准则是一致的。但是,我们可以通过设置ItemCountThreshold的值来强制其中的一个模式。只要记录的数量少于等于ItemCountThreshold,List picker会保持内联模式。Book Reader的font picker保持10种字体的内联模式,所以其属性值设置为10.
List picker定义了Header及其相关的HeaderTemplate属性,定义了ItemTemplate属性,用于自定义每个记录内联模式中的外观显示效果。即使我们使用全模式,对于list picker的外观来说,这些属性也是很重要的。对于全屏的列表,list picker同样定义了独立的FullModeHeader和FullModeItemTemplate属性。如图25.4所示,全屏模式的list picker利用了这两个属性。如果我们不指定FullModeItemTemplate,全模式会使用ItemTemplate。
使用全模式时,List pickers不能包含UI元素!
如果我们直接把诸如text blocks或者toolkit中的ListPickerItem控件这些UI元素放置于list picker中,那么,在全屏模式显示时,会抛出异常。那是因为该控件尝试将每个记录加入到额外的全屏模式列表中,但是单个UI元素一次只能放置于一个地方。解决方法是在list picker中放置非可视化的数据记录,然后使用模板来控制每个记录的外观。
避免将内联模式的list picker放置于scroll viewer的底部!
List picker在这种情况下不适合使用。当第一次展开时,屏幕内容不会被移动,这是为了确保内容保留在屏幕上。然后,当尝试着用滚动条来查看其他内容时,list picker会折叠起来。为了达到最好的性能,内联模式list picker下的元素应该将CacheMode设置为BitmapCache,那是因为list picker的下拉和收缩能够改变这些元素的位置。
List Pickers and Picker Boxes
虽然单个list picker控件提供了两种不同的体验,但是一些Windows Phone方面的文章将全模式的list picker称为分离的picker box控件,它保留了内联模式体验中的term list picker。这正是为什么第19章“Animation Lab”中使用的自定义控件称为PickerBox的原因。
The PaginatedDocument User Control
为了决定分页产生的位置,PaginatedDocument用户控件必须测量当前字体设置下,每个字符的宽度和高度。执行测量的唯一方式是将文本放置于text block中,并且检查其ActualWidth和ActualHeight属性的值。因此,PaginatedDocument将执行以下三个步骤的算法:
1. 查找文档中每个不同的字符(《傲慢与偏见》只包含了85个不同的字符)。
2. 通过向text block中放置每个字符来测量其宽度和高度,一次放置一个。所有字符的高度都是一样的(因为这里的高度是线高度,包含了填充和其它),因此,高度只需要测量一次。
3. 从头至尾浏览整篇文档,使用预先测量好的字符宽度来计算每个换行的地点。有了这个信息,以及之前测量的高度,我们就可以知道每个换页的地点。由于收缩部分单词的需求,决定换行符需要一些小技巧。
基于计算得到的换页地点和换行地点,该控件为每一行文字增加一个text block来得到所有的页面。
注意:
➔ 换行与换页的索引分别存储在各自的列表中。存储换页的列表是换行列表的子集,这种关系在一个页面需要渲染时变得尤为清晰。
➔ 在UpdatePagination中,将尽可能多的工作交给后台线程来做。因为实际的测量工作必须在UI线程中完成,但是,两个后台辅助线程用来将一个后台线程过渡为主线程,然后再将其转回后台线程。
➔ 本控件对于输入的文本有一些假设,工程中包含的《傲慢与偏见》的文档经过了预处理,使得下面的假设成立:
1. 换行符(\n)表示强制的换行,它只在一段文字的末尾出现(原文使用固定的行宽度,因此定期放置\n就可以,这就无法完成输出的动态调整)。
2. 回车符(\r)代表新一章的开始。有了它的帮助,这就完成了章节集合总数的自动统计,使得可以将其显示在主页面的list box上。