UI 前沿技术

完成电子书阅读器

Charles Petzold

下载代码示例

Charles Petzold
之前没有 Kindles 和 Nooks 和 iPads 和智能电话 ; 之前没有 HTML、 PDF、 XPS、 EPUB、 MOBI 和精简 ; 没有甚至由个人所拥有的计算机之前,没有项目谷登堡。始建于 1971 迈克尔 • s图表 (最近已停机 64 个年龄)、 项目谷登堡轻松是数字化的公共域簿的最早集合。在 15 万相比或使文本累计通过 Google 书籍,它的大约 35000 书籍的清单看起来 quaint,项目谷登堡仍保持宝贵的资源,用于访问经典赛事。

项目谷登堡书现在提供了几个富文本格式,但许多年的重点是完全在纯文本上,合理假定正在该 rtf 格式有待补充并转但永远是纯文本。开始逐渐增强此代码五个月前,当我的演示如何打开了项目 Gutenberg.As 到 Windows Phone,为编写分页逻辑列需要简单但大量的文本文件时,项目时间上有其自己的寿命。该程序现在已发展到成熟的电子书籍阅读器的 Windows Phone 项目谷登堡库使您可以访问的。我称之为 Phree 书籍阅读器 — 发音为"免费书籍读取"和"e-书籍阅读器"与 rhyming,但使用"电话"为"ph"拼写),可从 Windows Phone 市场上免费下载。像平常一样,您也可以下载该程序的源代码,从 MSDN 杂志 Web 站点。

目录和 Web 服务

建立一个大型的应用程序的方法有多种。某些开发人员喜欢开头的整体结构,并逐渐实现了更详细的代码的自上而下的方法。其他人更愿意从合并到功能更强大的程序集的低级例程开始自下而上的过程。

我倾向于获取内容的主要目标与混合使用两种方法 — — 任何东西 — — 尽可能快地工作。即使只是稍微有点职能的骨架的程序给了我妨碍,我重要积极的反馈,一旦我正在使用该程序,必需的增强功能变得明显。

如果您已经过去五个月阅读了此列,您知道我以前的电子书籍阅读器被限制为只是一本书或最近,有四部著作。这些书籍是作为内容绑定到应用程序可执行文件中。这种方法允许我重点完全放在同时避免杂乱作业的 Web 测试和甚至 messier 问题,即允许用户搜索书的标题和作者通过下载书籍的阅读体验。不过,我知道我将不得不最终解决这些难题。

项目谷登堡网站 (gutenberg.org) 试图使该用户可以方便地搜索和下载书籍,但它不实现公共的 Web 服务,以便其他应用程序可以执行类似的搜索。

存储在项目谷登堡每本书是由整数 ID 标识。如果某个程序知道特定的书籍的 ID 号,它可以下载 XML 文件在 gutenberg.org/ebooks/N.rdf,其中 N 是 ID 号。此资源描述格式或 RDF,文件通常是大约 10 KB 的大小,并包含书名、 作者和其他信息,以及书籍本身中几种不同格式的链接。此文件否则是很好,如果您知道所需书籍的 ID 号,但不是如此重要。

项目谷登堡还可提供的所有书籍的完整目录 gutenberg.org/feeds/catalog.rdf.zip 在其集合中。它是大小大约 9 MB 下, 解压到 200 MB 的 XML 文件。在该目录中的信息是类似 — — 但不是完全相同 — 单个 RDF 文件中的信息。随着新簿添加到项目谷登堡库存目录的新版本创建每一天。

刚开始我以为我的电子书籍阅读器无法下载整个目录的移动电话,并将其存储在独立存储中的搜索目的,但我担心大小。例如,平均一词是五个字符长,单词空格分隔,因此需要 50000 word 书籍以纯文本格式的仅 300 KB 的存储。解压缩的目录中,与此相反,占用的存储量 6000 多个书籍与相同!

然后遇到了另一个问题: 我无法打开并读取与常规的目录文件。通过将 XmlReaderSettings 的 ProhibitDtd 属性设置为 false 的 XmlReader NET 版本。但是,XmlReader 的 Silverlight 版本 choked 文件,和 XmlReaderSettings 的 DtdProcessing 属性没有设置 — 或任何其他内容我尝试 — 工作。

之后多预谋--,我决定编写我自己 Web 站点上承载的 Web 服务。Web 服务获取项目谷登堡站点中,从目录文件下其解压、 打开它、 分析它,然后将精简版本地存储在"平面"格式 — 每本书的一个文本行 — 用于快速搜索。

当然,任何时候,您处理的其他人的数据,您还处理其数据结构。我的 Web 服务实现方法命名参数指定标题和作者单词搜索,并返回达 25 个实例类型的我称为 GutenbergBook。此类包含有关该目录条目,包括标题 (和有时两个标题),从获得的零个或多个"者"(作者和可能是合著者),本书的信息和零个或多个参与者 (如翻译员和编辑器)。

此外包括在谷登堡目录是一个"友好的标题,"其中通常包含标题和作者),哪些是限制为 50 个字符。此友好的标题似乎理想对于导致搜索结果显示给用户,并用于标识简介册时正在读取它。

但此友好的标题并不总是那么友好。它非常适合于较短的标题,如简奥斯汀那个"好像由 Jane 年代,",但通常是 deficient 长的标题。例如,谷登堡目录中包含的各种版本和卷的爱德华 Gibbon 著名历史作品,12 项和所有 12 都具有相同的友好标题为 50 个字符被截断:"历史记录的拒绝和秋季的罗马 E."

给我这意味着如果我想要用于此友好的标题标识下载的书籍,我们需要为用户提供一些方法来编辑它以更有意义,如"谢绝和秋季的喷气式卷 3。

前端数据透视

最起码,电子书阅读器的前端需要显示已下载的书籍和下载更多书籍搜索屏幕的列表。它看起来很明显,这两项将数据透视表控件的一部分我 — Windows Phone 程序,用于在非可导航的页面格式呈现多个屏幕的常用控件。

前端数据透视表控件的 Phree 书籍阅读器有五个项目按以下顺序: 床、 库、 搜索、 请求和约。

尽管搜索是在数据透视表中的第三项,它是新用户开始的位置。如中所示图 1、 您键入标题词或作者的单词,和它发出的 Web 服务调用。高达 25 命中返回并显示在列表框中。每个命中标识 ID 号和从项目谷登堡目录友好的标题。

The Search Item of the Pivot Control
图 1 的数据透视表控件的搜索项

如中所示,该程序时用户点击这些项目之一,定位到下载页面中, 图 2。此页显示了从目录的其他信息,并允许您下载简介册。请注意讨论参与者项,以表明俄罗斯的宣传资料,Constance Garnett 著名翻译人员。

The Download Page Ready to Download
图 2 下载下载页已准备好

当您开始下载一本书时,通常您将看到突然发生更改的文件名。项目谷登堡目录中包含可用的各种格式的文件名 — — 包括用于我的目的,纯文本以 utf-8 编码的首选的格式 — — 但我发现某些文件是空的或已损坏。在单个 RDF 文件的文件名是不同和可靠得多。因此 Phree 书籍阅读器在开始下载一本书之前,它将下载 RDF 文件,并从的获取文件名。

簿下载后,下载页面将显示按钮导航到其他页。第一个按钮允许用户更改该程序是指作为"显示标题"。此标题从谷登堡目录最初设置为友好的标题,并且也仅限于 50 个字符。

第二个按钮涉及章符。项目谷登堡书籍中的空白行用于分隔章节号各不相同。此选项允许用户更改该条件,并移除多余的章断点。

数据透视表控件上的请求项是类似于搜索不同之处在于只需键入项目谷登堡 ID 号中的书籍,而不是搜索的术语。数据透视表控件然后导航到下载页。

简介册时已下载一本书,加入库,它是数据透视表控件上,第二项,如所示图 3

The Library Item of the Pivot Control
图 3 的数据透视表控件库项目

此库视图使用从目录条目,而不是显示标题的标题和作者信息。组织书籍的作者和标题。点击开始读取该简介册的标题之一。点击查看完整的目录信息 (类似于下载页) 和 (可选) 编辑显示标题和章节工间休息,或删除简介册的问号。

从未在我看来的任何疑问,我想 Phree 书籍阅读器库作者按字母顺序组织。这正是我虚构盘架的排列方式在家里,我不太 compulsive 有关按字母顺序排序的标题除外。我假设可能有那些愿意以不同的方式,有点排列库 Phree 书籍阅读器的用户,但我因此我确实不能打开,以在此问题上协商主要是为我自己编写了此程序!

显示按作者和标题的书籍是出色的应用程序的列表框分组。ItemsControl 的 Windows Presentation Foundation (WPF) 版本有一个非常好的属性,称为 GroupStyle,您可以在导航中定义的分组属性项的样式。在 Silverlight,您将使用 CollectionViewSource,和它不支持组,尽管 Silverlight ItemsControl 没有 GroupStyle。

相反,我使用 ItemsControl 集合的作者,如中所示为每个项目模板显示作者的姓名跟作者的标题,非可滚动列表框的位置, 图 4

图 4 库数据透视表数据项

<ScrollViewer Name="libraryScrollViewer"
              VerticalScrollBarVisibility="Auto">
  <ItemsControl Name="libraryItemsControl">
                     
    <!-- Assumes ItemsSource = AppSettings.Library.Authors -->
    <ItemsControl.ItemTemplate>
      <DataTemplate>
        <StackPanel>
          <!-- Creator -->
          <TextBlock Text="{Binding}"
                     FontWeight="Bold"
                     Margin="0 6" />
                                     
          <!-- Books -->
          <ListBox ItemsSource="{Binding Titles}"
                   ItemContainerStyle="{StaticResource listBoxItemStyle}"
                   SelectionChanged="OnLibraryListBoxSelectionChanged">
                                     
            <!-- Prevent scrolling of ListBox -->
            <ListBox.Style>
              <Style TargetType="ListBox">
                <Setter Property="ScrollViewer.VerticalScrollBarVisibility"
                        Value="Disabled" />
              </Style>
            </ListBox.Style>
                                     
            <ListBox.ItemTemplate>
              <DataTemplate>
                <Grid>
                  <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                  </Grid.ColumnDefinitions>
 
                  <Grid Grid.Column="0"
                        Margin="12 6"
                        VerticalAlignment="Center">
                    <Polygon Fill="{StaticResource PhoneAccentBrush}"
                             Points="6 0, 47 0, 47 57, 41, 63, 0 63, 0 6" />
                    <Image Source="Images/BookIcon.png" />
                  </Grid>
                                                 
                  <!-- Book title -->
                  <TextBlock Grid.Column="1"
                             Text="{Binding}"
                             FontSize="{StaticResource
                               PhoneFontSizeMediumLarge}"
                             VerticalAlignment="Center"
                             TextWrapping="Wrap" />
                                                  
                  <!-- Info Button -->
                  <Button Content=" ? "
Grid.Column="2"
                          Tag="{Binding ID}"
                          VerticalAlignment="Center"
                          Click="OnInfoButtonClick" />
                </Grid>
              </DataTemplate>
            </ListBox.ItemTemplate>
          </ListBox>
        </StackPanel>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
  </ItemsControl>
</ScrollViewer>

查找此方案的替代方法是在我的待办事项列表的 Phree 书籍阅读器。 我注意到,随着积累了很多在库中的更多书籍,初始加载时间开始受到影响,并且我怀疑嵌套的列表框控件为什么是原因。

人们有时引用到他们当前正在为"在我床边的表上的书籍。"阅读的图书因此,第一个数据透视表数据项标有"床边",并显示最多包含六本书,按日期和上次读取时间的降序排序。 每本书都标识与它显示的标题。

顺便说一下,Windows Phone 是带有指示灯关闭阅读床的好方法。

最后一个数据透视项目标记为"关于",包含有关程序,以及链接到我过去某些帮助信息 MSDN 杂志有关电子书籍阅读器的列。

对面支枢

如我在此前端开发,我最大的包括所涉及的数据透视表控件本身和导航离开并返回到数据透视表控件。

通常该程序启动时,默认数据透视项目应床。 该程序应使其尽可能容易恢复朗读最近读过的书。 但是,如果用户尚未阅读任何书籍,床边列表将为空以便库视图应为第一个向上。 如果用户没有下载任何书籍和 — — 可能是因为第一次运行该程序 — 搜索项应为默认值。

以编程方式控制数据透视表控件被通过数据透视表的 SelectedIndex 属性设置。 但是,在已加载了数据透视表控件之前,不能设置此属性。 但是,已加载了数据透视表控件后,设置该属性具有明显滑动到该项目的数据透视表控件的效果。 我认为我更愿意小于可见的过渡。

下载书籍时,获取 messier 涉及的数据透视表控件的逻辑。 如果用户从该搜索项定位到下载页面,而无需下载书籍下按下了电话的后退键导航应该返回到搜索项。 但是,如果下载简介册,然后下载页面应该回到库数据透视表数据项,现在列出书籍。 如果簿下载和用户选择跳转到阅读视图,然后返回到主页页面导航应该会导致不可见,请再次以显示只需阅读书籍的床边项。

我发现我自己使用的 PhoneApplicationService 状态词典来跟踪程序已经和正在进行的工作。 搜索和透视项目设置名为"downloadBook",导航到下载页中,并且该页面设置"successfulDownload"或"successfulDownloadAndRead"字典条目,具体取决于是否之前状态字典条目的请求用户已选择或不跳转到阅读视图。 我并不十分满意的这一做法,inelegance,也许将来我会发现工作有了更好的东西。

像平常一样,逻辑删除

很明显较大程序获取,nastier 的逻辑删除问题变得。 请考虑在中显示的搜索屏幕图 1。 我编写 Web 服务返回仅 25 命中,而且还可以让程序即可获得每个其他调用由屏幕上的底部按钮触发其他 25。 最终可能会有数百个 — — 甚至上千 — 中列表框,具体取决于具体的搜索条件的项目。

这是一个棘手的逻辑删除区域的一个很好的示例。 在列表框中的所有数据无法都再生通过再次调用 Web 服务,但这会打开太多的时间。 必须先保存列表框中的项。 但什么您不想执行是保存和还原 OnNavigatedFrom 中的内容和 OnNavigatedTo 重写。 此搜索控件在程序的主页面,数据透视表项的一部分,并且没有导航并不涉及逻辑删除此网页和从大量。 保存和还原中导航的重写,但不是成千上万个列表框中的项的小对象它没问题。 当应用程序实际上被逻辑删除时,应仅保存列表框内容。

在此程序中,我体验这样精确的通用技术 — — 我可以在应用程序类中定义一个名为 TombstoneObjects 属性:

public IDictionary<string, object> TombstoneObjects { set; get; }

在程序中任何位置的任何类可以使此字典的使用。SearchControl 类实现最后一个月的专栏中介绍的 ITombstonable 接口。SaveState 方法中 (称为 MainPage 的 OnNavigationFrom 重写从),该控件将列表框的内容复制到列表对象,并将的保存到 TombstoneObjects 词典。在 RestoreState 方法中,它将恢复内容的列表框,但只,如果列表框为空。

在 App 类负责保存和恢复到状态词典的 PhoneApplicationService TombstoneObjects 的内容,因为在 App 类具有强大的智能地执行此操作。它知道何时该程序正在逻辑删除因为它已安装的 PhoneApplicationService 事件处理程序。其结果是很少的多余工作发生如果程序不实际上正在逻辑删除。

虽然我写了 Phree 书籍阅读器为 Windows Phone 7,在时间我撰写这我已经开始与 beta 版本的下一个版本。在 Windows Phone 7.5,应用程序是已逻辑删除次数较少,因此它是为这些应用程序,以避免大量不必要的逻辑删除工作双重重要。

说到下一版本的 Windows Phone,我已经积累了我想我将它取决于升级时将添加到 Phree 书籍阅读器的功能的列表。重新或许我们将能访问在今后的专栏中的程序。

Charles Petzold是 longtime 的特约编辑 MSDN 杂志。他最近的书籍,"编程 Windows Phone 7"(微软出版社,2010年) 是可在免费下载 bit.ly/cpebookpdf

这要归功于以下技术专家审阅这篇文章: 陈德贝利