Este artigo foi traduzido por máquina.

Fronteiras da interface do usuário

Concluindo o leitor de livros eletrônicos

Charles Petzold

Baixar o código de exemplo

Charles PetzoldAntes de haver Kindles e existe e iPads e smartphones; antes que houve em HTML, PDF, XPS, EPUB, LULAR e Plucker; antes de haver mesmo computadores pertencente a indivíduos, não havia Gutenberg Project. Fundada em 1971 por Michael s. Hart (que morreu recentemente com a idade de 64), o projeto Gutenberg facilmente é a mais antiga coleção de livros de domínio público digitalizadas. Enquanto seu inventário de livros de cerca de 35.000 parece quaint, quando comparado de 15 milhões ou para que textos acumulavam por livros do Google, Gutenberg Project permanece um recurso de valor inestimável para acessar os clássicos.

Livros de Gutenberg Project agora estão disponíveis em vários formatos de rich text, mas por muitos anos que o foco era inteiramente em texto sem formatação, a suposição razoável, sendo que rich-text formata vêm e vá mas texto sem formatação é indefinidamente. Cinco meses atrás, quando eu precisava de um arquivo de texto simples, mas substancial para uma coluna que demonstra como escrever a lógica de paginação para Windows Phone, eu desativei o projeto Gutenberg.As, comecei a aprimorar progressivamente esse código, o projeto levou uma vida própria. O programa agora se tornou um leitor de e-book completo para Windows Phone que permite o acesso à biblioteca do Gutenberg Project. Eu chamo o leitor de catálogo de Phree — pronunciado "leitor book gratuito" e rhyming com "leitor de e-book", mas escrito com "ph" para "telefone" — e está disponível como um download gratuito do mercado de Windows Phone. Como de costume, você também pode baixar código fonte do programa a partir do MSDN Magazine site da Web.

Catálogos e serviços da Web

Existem muitas maneiras de criar um aplicativo grande. Alguns desenvolvedores como uma abordagem de cima para baixo que começa com a estrutura geral e gradualmente implementa o código mais detalhado. Outros preferem um processo de baixo para cima começando com as rotinas de baixo nível que são combinadas em assemblies mais potentes.

Eu tendem a misturar as duas abordagens com o principal objetivo de obter algo — nada — trabalhar mais rápido possível. Até mesmo um programa de esqueleto que é apenas um pouco funcional me dá o feedback positivo essencial que me indo e depois que eu estou usando o programa, as melhorias necessárias ficam evidentes.

Se você tem lido nesta coluna para os últimos cinco meses, sabe que meus leitores de e-book anteriores eram limitadas a apenas um livro ou, mais recentemente, quatro livros. Esses livros foram agrupados para o executável do aplicativo como conteúdo. Essa abordagem me permitiu enfocar inteiramente a experiência de leitura, evitando o trabalho confuso de fazer o download de livros sobre a Web e o problema ainda mais complicado de permitir que os usuários para pesquisar livros por título e autor. Mas sabia que eu teria que atender a esses desafios eventualmente.

Site do Gutenberg Project (gutenberg.org) tenta torná-lo mais fácil para os usuários pesquisem e baixar manuais, mas ele não implementa um Web service público para que outros aplicativos possam executar pesquisas similares.

Cada livro armazenado no Project Gutenberg é identificado por uma identificação de número inteiro. Se um programa conhece o número de identificação para um determinado livro, ele pode fazer o download de um arquivo XML no gutenberg.org/ebooks/N.rdf, onde n é o número de identificação. Este formato de descrição do recurso ou RDF, arquivo geralmente é cerca de 10 KB de tamanho e contém o título do livro, autor e outras informações, juntamente com links para o catálogo em vários formatos diferentes. Este arquivo é ótimo se você souber o número de identificação de um livro desejado, mas não é tão bom, caso contrário.

Projeto Gutenberg também disponibiliza um catálogo completo de todos os livros em sua coleção no gutenberg.org/feeds/catalog.rdf.zip. É de cerca de 9 MB de tamanho e a descompactação é um arquivo XML de MB de 200. As informações neste catálogo são semelhantes — mas não idênticos — as informações nos arquivos RDF individuais. Uma nova versão do catálogo é criada diariamente, como livros novos são adicionados ao estoque Gutenberg Project.

Inicialmente, pensei meu leitor de e-book foi possível fazer o download de todo o catálogo para o telefone e armazená-lo no armazenamento isolado para fins de pesquisa, mas eu estava preocupado com o tamanho. Por exemplo, a palavra a média é de cinco caracteres e palavras são separadas por espaços, portanto, um livro de 50.000-word no formato de texto simples requer apenas 300 KB de armazenamento. O catálogo descompactado, em contraste, consome a mesma quantidade de armazenamento que mais de 6.000 livros!

Em seguida, encontrei outro problema: eu poderia abrir e ler o arquivo de catálogo com normal.NET do XmlReader, definindo a propriedade de ProhibitDtd do XmlReaderSettings como falsa. No entanto, a versão do Silverlight do XmlReader ficava o arquivo e nenhuma configuração da propriedade DtdProcessing do XmlReaderSettings — ou qualquer outra coisa eu tentei — trabalhou.

Depois de muito foram, eu decidi escrever minha própria serviço da Web hospedado em meu site. Que o serviço da Web obtém o arquivo de catálogo no site do Gutenberg Project, a descompactação é ele, o abre, analisa e armazena uma versão sem recursos localmente em um formato "flat" — uma linha de texto por um livro — para agilizar a pesquisa.

É claro que, sempre que você está lidando com dados de outra pessoa, você também lidando com suas estruturas de dados. Meu serviço da Web implementa um método chamado pesquisa com a especificação de palavras do título e autor de argumentos e retorna até 25 instâncias de um tipo chamado de GutenbergBook. Essa classe incorpora informações sobre o livro obtida a partir da entrada do catálogo, incluindo um título (e, às vezes, os dois títulos), zero ou mais "criadores" (autor e possivelmente co-autores) e zero ou mais colaboradores (como tradutores e editores).

Também incluído no catálogo do Gutenberg é um "título amigável," que geralmente incorpora o título e o autor, e que é limitado a 50 caracteres. Esse título amigável parecia ideal para mostrar os resultados da pesquisa para o usuário e para identificar o livro quando ele está sendo lido.

Mas esse título amigável nem sempre é tão amigável. Ele é ótimo para títulos de curtos, como, por exemplo, "Emma por Jane Austen," mas costuma ser deficient para títulos de mais tempo. Por exemplo, o catálogo de Gutenberg contém 12 entradas para as diversas edições e volumes de opus de históricos famoso do Edward Gibbon, e todos os 12 têm o mesmo título amigável truncado para 50 caracteres: "e O histórico do recusar e queda do Roman".

Para mim isso significava que, se eu fosse usar este título amigável para identificar um catálogo baixado, eu precisaria dar ao usuário de alguma maneira para editá-lo para algo mais significativo, como, por exemplo, "Recusar e queda do Império romano, Volume 3".

O pivô front-end

Na pior das hipóteses, o front-end de um leitor de e-book precisa exibir uma lista de livros baixados e uma tela de pesquisa para download mais livros. Pareceu óbvio para mim que esses dois itens seria parte de um controle de tabela dinâmica — um controle popular para programas de Windows Phone para apresentar várias telas em um formato diferente de páginas navegáveis.

O controle de dinâmica de front-end do leitor de catálogo de Phree tem cinco itens na seguinte ordem: na beira do leito, a biblioteca, a pesquisa, a solicitação e em sobre.

Embora a pesquisa é o terceiro item o pivô, é onde começará um novo usuário. Conforme mostrado na a Figura 1, você pode digitar palavras de título ou o autor de palavras e ele faz uma chamada ao serviço da Web. Até 25 acertos são retornados e exibidos em um ListBox. Cada visita é identificada pelo número de identificação e título amigável a partir do catálogo do Project Gutenberg.

The Search Item of the Pivot Control
Figura 1 O Item de pesquisa do controle dinâmico

Quando o usuário toca um desses itens, o programa navega para uma página de download, conforme mostrado na a Figura 2. Esta página mostra informações adicionais do catálogo e permite que você baixe o catálogo. Observe a entrada colaborador indicando o famoso tradutor de russo literatura, Constance Garnett.

The Download Page Ready to Download
Figura 2 Download página pronto para Download

Quando você iniciar o download de um livro, geralmente você verá o nome do arquivo repentinamente mudam. O catálogo de Gutenberg Project contém nomes de arquivos para os diversos formatos disponíveis — incluindo o formato preferido para meus objetivos, o texto sem formatação, codificado em UTF-8 — mas descobri que alguns desses arquivos estavam vazios ou corrompido. Os nomes de arquivos em arquivos individuais RDF eram diferentes e muito mais confiável. Então, antes que o leitor de catálogo de Phree começa a fazer o download de um livro, baixa o arquivo de RDF e obtém um nome de arquivo do que.

Depois de baixar o catálogo, a página de download exibe botões navegar para outras páginas. O primeiro botão permite que o usuário altere o que o programa se refere como o "título de exibição". Esse título originalmente é definido para o título amigável do catálogo do Gutenberg e também está limitado a 50 caracteres.

O segundo botão envolve quebras de capítulo. Livros de Gutenberg Project variam o número de linhas em branco, usado para separar os capítulos. Esta opção permite que um usuário alterar esse critério e remover quebras de capítulo supérfluas.

O item de solicitação de controle dinâmico é semelhante ao pesquisar, exceto que você simplesmente digita um número de identificação do projeto Gutenberg para termos um livro, em vez de pesquisa. Controle dinâmico, em seguida, navega para a página de download.

Quando um livro tenha sido descarregado, o livro une a biblioteca, que é o segundo item no controle dinâmico e é mostrada na a Figura 3.

The Library Item of the Pivot Control
Figura 3 O Item de biblioteca do controle dinâmico

Este modo de exibição de biblioteca usa as informações de título e autor da entrada do catálogo, em vez do título do vídeo. Livros são organizados por autor e título. Toque em um dos títulos para iniciar a leitura desse livro. Toque no ponto de interrogação para ver as informações do catálogo completo (semelhantes à página de download) e, opcionalmente, edite o título de exibição e as quebras de capítulo ou excluir o livro.

Nunca houve qualquer dúvida em minha mente que queria que a biblioteca do leitor de catálogo de Phree organizada em ordem alfabética por autor. É exatamente como o Meus prateleiras de ficção estão organizadas em casa, exceto que eu não sou tão compulsive sobre colocar em ordem alfabética dos títulos. Eu suponha que haja usuários do Reader de catálogo de Phree que prefeririam que a biblioteca de serem organizada um pouco diferente, mas que escrevi este programa principalmente para mim, para que eu realmente não aberto para negociação sobre esse problema!

A exibição de livros por autor e título é um ótimo aplicativo de agrupamento de ListBox. A versão Windows Presentation Foundation (WPF) ItemsControl tem uma propriedade ótimas chamada GroupStyle que permite definir um estilo para uma propriedade de agrupamento de itens em uma CollectionView. No Silverlight, você usaria CollectionViewSource e, embora ele oferece suporte a grupos, Silverlight ItemsControl não tem um GroupStyle.

Em vez disso, usei ItemsControl para a coleção de autores, onde o modelo para cada item exibe o nome do autor, seguido de uma caixa de listagem não rolável para títulos do autor que, conforme mostrado na a Figura 4.

Figura 4 O Item de biblioteca dinâmica

<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>

Encontrar uma alternativa para este esquema está na minha lista de tarefas para o leitor de catálogo de Phree. Tenha notado que eu acumular mais livros na biblioteca, o tempo de carregamento inicial começa a sofrer e suspeitar de que os controles ListBox aninhados são o motivo por que.

As pessoas às vezes, consultem os manuais que está lendo neste momento como "os livros em minha tabela à beira do leito." Por esse motivo, o primeiro item da tabela dinâmica é rotulado como "à beira do leito" e mostra um máximo de seis livros, classificados em ordem decrescente de data e hora última leitura. Cada livro é identificado com seu título de exibição.

A propósito, uma Windows Phone é uma ótima maneira de ler na cama com as luzes desativadas.

O último item da tabela dinâmica é rotulado como "sobre" e contém algumas informações da Ajuda sobre o programa, bem como links para o meu passado MSDN Magazine colunas sobre o leitor de e-book.

Dinamizar a gira em torno de

Como eu estava desenvolvendo este front-end, Meus maiores lutas envolvido o próprio controle de tabela dinâmica e navegação longe e volta para o controle dinâmico.

Normalmente quando o programa é iniciado, o item da tabela dinâmica padrão deve ser na beira do leito. O programa deve torná-lo o mais fácil possível continuar a ler o livro de leitura mais recentemente. No entanto, se o usuário ainda não foram lidas qualquer livros, à beira do leito lista estará vazia para que o modo de exibição de biblioteca deve ser o primeiro backup. E se o usuário ainda não baixou qualquer livros — talvez porque o programa está sendo executado pela primeira vez — o item de pesquisa deve ser o padrão.

Controlar por programação um controle de tabela dinâmica é realizada, definindo a propriedade SelectedIndex de pivô. No entanto, essa propriedade não pode ser definida antes que o controle dinâmico foi carregado. Mas após o controle dinâmico tiver sido carregado, definindo a propriedade tem o efeito de visivelmente deslizando controle dinâmico para aquele item. Acho que eu prefere uma transição de menos visível.

Complica ainda mais lógica envolvendo o controle de tabela dinâmica quando um livro é baixado. Se o usuário navega do item de pesquisa para a página de download e, em seguida, pressiona a tecla de voltar do telefone sem baixar o catálogo, navegação deve voltar para o item de pesquisa. No entanto, se o livro for baixado, em seguida, a página de download deve voltar para o item de biblioteca dinâmica onde o livro agora está listado. Se o catálogo é baixado e o usuário optar por ir diretamente para o modo de exibição de leitura, navegação, em seguida, voltar para a home page deve causar o item à beira do leito devem ficar visíveis novamente para mostrar o livro que acabo de ler.

Me encontrei usando o dicionário de estado do PhoneApplicationService para controlar onde foi o programa e o que está fazendo. A pesquisa e a solicitação de itens definir uma entrada do dicionário de estado chamada "downloadBook" antes de navegar para a página de download e essa página define uma entrada do dicionário "successfulDownload" ou "successfulDownloadAndRead" dependendo se o usuário escolheu saltar para o modo de exibição de leitura ou não. Não estou muito satisfeito com a inelegance dessa abordagem e talvez no futuro vai encontrar algo que funciona um pouco melhor.

Como sempre, a marcação para exclusão

Obviamente, obtém um programa maior, tornam-se a nastier problemas de marcação para exclusão. Considere a tela de pesquisa mostrada na a Figura 1. Escrevi o serviço da Web para retornar somente 25 acertos, mas também para permitir que um programa obter um 25 adicionais com cada chamada adicional disparado pelo botão inferior na tela. Eventualmente pode haver centenas — até mesmo milhares — de itens na caixa de listagem, dependendo a especificidade dos critérios de pesquisa.

Este é um bom exemplo de uma área complicada de marcação para exclusão. Todos os dados na caixa de listagem poderiam ser regenerados chamando o serviço da Web novamente, mas isso o levaria muito tempo. Os itens na caixa de listagem devem ser salvo. Mas o que você não deseja fazer é salvar e restaurar o conteúdo da OnNavigatedFrom e substitui a OnNavigatedTo. Esse controle de pesquisa faz parte de um item da tabela dinâmica na página principal do programa e há muito de navegação para e partir dessa página que não envolve a marcação para exclusão. Há problemas salvar e restaurar os objetos pequenos as substituições de navegação, mas não de milhares de itens em um ListBox. O conteúdo da caixa de listagem só deve ser salvos quando o aplicativo está realmente sendo marcados para exclusão.

Para este programa, eu tenha achado com uma técnica de finalidade geral para fazer exatamente isso — na classe App, defini uma propriedade chamada TombstoneObjects:

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

Qualquer classe em qualquer lugar no programa pode fazer uso desse dicionário. A classe SearchControl implementa a interface de ITombstonable que abordei na coluna do mês passado. No método SaveState (que é chamado de substituição OnNavigationFrom do MainPage), o controle copia o conteúdo da caixa de listagem para um objeto List e salva que o dicionário de TombstoneObjects. No método RestoreState, ele restaura o conteúdo da caixa de listagem, mas somente se a caixa de listagem está vazia.

A classe de aplicativo é responsável por salvar e restaurar o conteúdo de TombstoneObjects para o dicionário de estado de PhoneApplicationService porque a classe de aplicativo tem o poder de fazer isso de maneira inteligente. Ele sabe quando o programa está sendo marcados para exclusão porque ele tem instalado manipuladores para os eventos de PhoneApplicationService. O resultado é que muito pouco trabalho estranha ocorre se o programa não está realmente sendo marcados para exclusão.

Embora eu escrevi Phree livro Reader para Windows Phone 7, no momento estou escrevendo isso que tenho trabalhado com versões beta da próxima versão. 7.5 De Windows Phone, aplicativos são marcados para exclusão com menos freqüência, por isso é duplamente importante para esses aplicativos evitar muito trabalho desnecessário de marcação para exclusão.

Falando da próxima versão do Windows Phone, eu já tenha acumulado uma lista dos recursos que eu gostaria de adicionar ao leitor de catálogo de Phree quando eu o assunto para a atualização. Talvez nós vai revisitando o programa em colunas futuras.

Charles Petzold é editor colaborador há muito tempo para MSDN Magazine. Seu livro mais recente, "Programação Windows Phone 7" (Microsoft Press, 2010), está disponível como um download gratuito, bit.ly/cpebookpdf.

Graças ao especialista técnico seguir pela revisão deste artigo: Richard Bailey