Windows App - Criar Aplicação Estilo Modern UI

Por Renato Haddad

Abril, 2013

Dn205307.060DE5057573180CEC6D227C6D3E2207(pt-br,MSDN.10).png

Visão Geral

O Cardápio Eletrônico é uma série de exercícios preparados de forma passo a passo para que você possa aprender todos os recursos que o Widows 8 oferece com aplicações no Windows 8 estilo Modern UI. Este exercício consiste desde a criação do projeto baseado num modelo existente, o entendimento do mesmo, como explorar a parte de interface de usuário, associar uma coleção de objetos, que neste caso, será uma coleção de pratos disponíveis no restaurante, assim como os recursos que interagem com as APIs do Windows 8, por exemplo, o Compartilhar (Share), o Pesquisar (Search), uso da WebCam, painel de configurações e como simular uma compra direto na loja do Windows (Windows Store).

Mas o que é o estilo Modern UI?

O estilo Modern UI é um conjunto de padrões de interface de usuário criado pela Microsoft para padronizar a forma de interagir e mostrar as informações ao usuário. Isto compreende desde a forma de como dispor imagens, textos, bordas, cores e estilos até a maneira que o usuário irá manipular a aplicação. Cabe ressaltar que o principal foco é o uso de Tablets, onde a interação ocorre através de toque na tela. Claro que é possível usar a mesma aplicação no desktop com o mouse.

Aproveitando o momento, é válido dizer que cada vez mais as aplicações estão sendo orientadas à serviços, onde toda a lógica, a regra de negócio e o banco de dados estão armazenados na nuvem (Azure). Desta forma, as aplicações instaladas irão consumir os serviços disponíveis na nuven, seja um Web Service, um WCF (Windows Communication Foundation) ou Web API, os quais podem retornar um JSON, um XML, etc.

Nesta série de exercícios iremos criar uma projeto estilo Modern UI lindo, funcional, focado no mundo real que aborda os principais recursos do Windows 8. No final da série você saberá como implementar na sua aplicação os seguintes recursos chave do estilo Modern UI:

  • Controles do Windows 8 como GridView, ListView, FlipView, AppBar e Semantic Zoom;
  • Experiência de usuário que trabalha com diversas escalas de exibição grande e pequena, zoom semântico, orientação vertical e horizontal e snap;
  • Integração com Charms do Windows 8 através das funcionalidades de configurações e contratos de pesquisar e compartilhar;
  • Aprender como tratar o ciclo de vida da aplicação, como salvar e restaurar valores armazenados nas configurações do Roam (Azure) onde os usuários podem usar através de diversos dispositivos;
  • Integração com o hardware para implementar a captura de foto e vídeo através das APIs do Windows 8;
  • Fixar na tela inicial um Tile secundário, enviar notificações para manter a aplicação viva e ativa para o usuário final;
  • Integração com as APIs do Windows 8 para a loja e simular compras da versão Trial.

Quais linguagens usaremos?

Em todos os exercícios usaremos o XAML (eXtensible Application Markup Language) para interface de usuário e o Visual C# para a programação. Nesta primeira etapa o C# será usado para criar a aplicação, implementar a navegação, conectar com a fonte de dados e mostrar o conteúdo nos controles através do conceito de Binding. Cabe ressaltar que os dados podem estar localmente ou fazer o download do Windows Azure.

Objetivos

Neste lab você aprenderá:

  • Criar uma nova aplicação estilo Modern UI usando o template do Visual Studio
  • Entender a estrutura do projeto e os arquivos incluídos
  • Preparar o projeto com novas imagens e fontes de dados
  • Usar a classe HttpClient para retornar dados do Windows Azure
  • Consumir e fazer o Binding dos dados no controle GridView
  • Usar os templates de dados para customizar a apresentação no controle ListView
  • Alterar o código C# e o XAML gerados pelo Visual Studio para customizar a UI da aplicação

Requirementos do Sistema

Você deverá ter os seguintes itens para completar os exercícios:

  • Microsoft Windows 8
  • Microsoft Visual Studio 2012 RC

Configuração

Você deverá seguir estes passos para preparar o seu computador para estes exercícios:

  • Instalar o Microsoft Windows 8
  • Instalar o Microsoft Visual Studio 2012 RC

Exercício 1: Criar Aplicação Estilo Modern UI

  • Neste primeiro exercício, você criará uma nova solução no Visual Studio com o projeto C# Windows Store Grid Application. Em seguida irá analisar os arquivos gerados pelo Visual Studio e fazer alterações para customizar a interface de usuário.

Tarefa 1 – Criar o Projeto

O primeiro passo é criar um novo projeto com códigos e recursos para a aplicação chamada Contoso Cookbook.

  1. Inicie o Visual Studio e selecione o menu File - New Project, ou na página principal, clique em New Project. Selecione a linguagem Visual C# e a opção Windows Store. Na lista de templates, selecione Grid App (XAML). O nome da aplicação deverá ser obrigatoriamente ContosoCookbook, isto porque você irá adicionar alguns arquivos prontos com este mesmo namespace, e não ocorrerão erros de compilação. Em Location você pode selecionar qualquer pasta no seu computador. Veja na Figura 1 os detalhes. Clique no botão OK e aguarde a criação do projeto.

Dn205307.FBD0B8FD1E810A0255111130B409E9E3(pt-br,MSDN.10).png

Figura 1 - Criação do projeto ContosoCookbook

  1. Selecione o menu Debug / Start Debugging (ou pressione F5) para executar a aplicação em modo de Debug. Como você pode visualizar na Figura 2, esta aplicação mostra um conjunto de dados agrupados pelo Título 1, 2, etc. Esta é a página inicial da aplicação.

Dn205307.BB01E92CAA9A35397B3C025E2F58CD32(pt-br,MSDN.10).png

Figura 2 – Página inicial da aplicação

  1. Vamos explorar esta aplicação por alguns minutos. Use o mouse ou os dedos, caso esteja rodando em um monitor de toque ou tablet. Navegue no sentido horizontal e note os demais grupos. No caso do mouse, uma dica é usar o rolamento (scroll) para se deslocar.
Dn205307.note(pt-br,MSDN.10).gifNota:
O layout do grid e a rolagem horizontal faz parte do controle GridView, o qual faz parte de muitos controles suportados pelo namespace WinRT Windows.UI.Xaml.Controls para criar controles ricos em recursos.

  1. Veja o que acontece quando você seleciona o Item Title: 1 no GridView. São mostrados todos os detalhes deste item, é o que chamamos de página detalhe. Na Figura 3 você nota que as informações são exibidas num layout padrão de acordo com o modelo usado na criação do projeto.
Dn205307.note(pt-br,MSDN.10).gifNota:
Windows 8 é frequentemente descrito como um sistema operacional “toque primeiro”, mas suporta diversos dispositivos como mouses e canetas. Disto isto, a partir de agora, todas as vezes que eu me referir a tocar, lembre-se que pode ser com o dedo, mouse ou caneta.

Dn205307.3BCAE1B4DB5069A9F48E29F61C360978(pt-br,MSDN.10).png

Figura 3 – Página de detalhe do item

Dn205307.note(pt-br,MSDN.10).gifNote:
Um ponto interessante a ser relevado é que esse item pertence a uma coleção de um grupo, portanto, se você estiver na página de detalhe do item, note do lado direito da tela que há uma seta indicando o próximo item. O mesmo ocorre se você estiver posicionado em um item do meio de uma lista, automaticamente é identificado que há item atrás e na frente. Neste caso teremos duas setas, anterior e próximo. Isto é uma das funcionalidades do fantástico controle FlipView, pertencente ao Windows.UI.Xaml.Controls.

  1. Agora retorne para o menu principal. Clique na seta para esquerda dentro de um círculo no cabeçalho (parte superior).
  2. O próximo passo é visualizar a página do grupo com os respectivos itens. No menu principal, clique no título “Group Title: 1” abaixo no nome “ContosoCookbook”. Veja na Figura 4 os detalhes do grupo com os itens.

Dn205307.526969A7F64258A72429F2E103586542(pt-br,MSDN.10).png

Figura 4 – Detalhes do grupo com os itens

  1. Pronto, agora que já navegamos em todas as páginas do template usado para a estrutura, volte para o Visual Studio. Aqui você tem diversos caminhos para parar a execução da aplicação. Por exemplo, dê um ALT + TAB para ir para o Visual Studio. Selecione o menu Debug / Stop Debugging ou na barra de ferramenta, clique no ícone bloco vermelho. Outra maneira é usar o ALT + F4 para fechar a aplicação que está sendo executada. E se você estiver usando um tela de toque ou um tablet, a melhor forma é posicione o dedo na parte superior da aplicação, pressione e arraste-o para baixo até o rodapé do tablet. Isto fechará a aplicação. Vale dizer que este procedimento também pode ser feito com o mouse no desktop ou notebook.

Tarefa 2 – Estrutura do Projeto

Está claro que quando o Visual Studio gera o projeto, ele poupa um bom trabalho de escrever códigos, classes e layouts. Especialmente em relação as páginas XAML, a parte lógica e interface de usuário (UI) com a estrutura de navegação entre as páginas (incluindo botões de retornar), exemplos com dados formatados e a lógica das classes. Agora vamos customizar o projeto criado pelo Visual Studio, para isto é preciso conhecer os bastidores do projeto, a estrutura, as classes, onde estão os dados, o layout, etc.

  1. Abra a janela do Solution Explorer (Figura 5) e veja o conteúdo da raiz do projeto. Existem quatro arquivos chave descritos a seguir, com os devidos códigos C#:
    • App.xaml – representa a aplicação e os recursos
    • GroupedItemsPage.xaml – representa a página inicial
    • ItemDetailPage.xaml – representa a página de detalhes do ítem
    • GroupDetailPage.xaml – representa a página do grupo com os respectivos itens

Dn205307.5F521BD5C46F97B257E68B336FBEEC2D(pt-br,MSDN.10).png

Figura 5 – Projeto com os itens

  1. Abra a pasta Assets e veja todas as imagens usadas no projeto.
  2. Abra a pasta Common, o qual contém diversos arquivos (Figura 6) e quero destacar:
  3. BindableBase.cs – contém os códigos para auxiliar o Binding quando ocorrer uma alteração na fonte de dados ou na UI. Quando temos uma coleção de dados usada na UI (GridView, por exemplo), é preciso implementar o INotifyPropertyChanged para notificar a classe que houve uma alteração em uma respectiva propriedade.
  4. BooleanNegationConverter.cs – é uma classe que herda o IValueConverter para controlar se um objeto será verdadeiro (true) ou falso (false). Toda e qualquer classe que herdar de IValueConverter chamamos de criar um conversor, o qual o único objetivo é receber um objeto e tratá-lo. Isto pode ser uma string, um objeto, uma data, enfim, qualquer tipo de dado. A entrada e a saída deste método é um objeto e você monta o código de acordo com a condição exigida.
  5. BooleanToVisibilityConverter.cs – é um conversor para controlar a propriedade Visibility de um objeto. A entrada será um Boolean (true/false) e o retorno será Visibility.Visible ou Visibility.Collapsed. Cabe ressaltar que no XAML para ocultar ou não um objeto você usa a propriedade Visibility.
  6. LayoutAwarePage.cs – esta classe é usada para dispor o layout da aplicação.
  7. StandardStyles.xaml – contém todos os estilos aplicados ao XAML.
  8. SuspensionManager.cs – esta classe é responsável por salvar e restaurar os dados quando a aplicação é suspensa ou ativada.

Dn205307.67AF6A79A8B2DD1F1BBE55DF52613B5F(pt-br,MSDN.10).png

Figura 6 – Arquivos co Common

  1. Abra a pasta DataModel e veja um arquivo chamado SampleDataSource.cs que contém as definições das classes de dados e os dados em si. Este arquivo é fundamental, todas as três páginas de layout o usam como fonte de dados, além de ser um excelente ponto de partida para você aprender como se desenvolver.

Tarefa 3 – Customizar a Página Inicial

Atualmente o nome do projeto exibido no topo da página principal é o mesmo nome do projeto (ContosoCookbook), e nem sempre é a melhor identificação. Veja os passos para alterar o “Contoso Cookbook”.

  1. No Solution Explorer dê um duplo clique no arquivo App.xaml.
  2. Note que há uma estrutura XML neste arquivo. Localize a string de resource chamada “AppName” e altere o valor de “ContosoCookbook” para “Contoso Cookbook”, como a seguir:
<x:String x:Key="AppName">Contoso Cookbook</x:String>
  1. Pressione F5 para executar a applicação no modo debug. Veja que o texto do título já exibe a alteração, conforme a Figura 7.

Dn205307.23813A4626C5CE03522DF056EC56488F(pt-br,MSDN.10).png

Figura 7 – Texto alterado na página inicial

  1. Finalize a execução da aplicação com ALT + F4 ou retorne ao Visual Studio com ALT + TAB e clique em Stop Debugging para finalizar aplicação.

Tarefa 4 – Customizar a Aplicação

Se você for na tela inicial do Windows 8 verá um Tile (ícone ou ladrilho) chamado ContosoCookbook (Figura 8). Este é o que chamamos de Tile primário, o qual foi criado quando a aplicação foi instalada. Isto ocorrerá toda vez que você executar uma aplicação no Visual Studio pela primeira vez. A posição onde o Tile será colocado na primeira vez é no espaço disponível do lado direito da tela, porém, nada impede de você reposicioná-lo. Para isto, basta clicar sobre o Tile, segurá-lo e arrastá-lo até a posição desejada.

Dn205307.117A6C7C55810E07C5B576F863AC4343(pt-br,MSDN.10).png

Figura 8 – Tile na tela inicial do Windows 8

Como mencionado anteriormente, a pasta Assets contém todas as imagens desta aplicação, e a imagem do Tile é a Logo.png. Cabe ressaltar que as imagens no Windows 8 devem ter como extensão apenas .png e .jpg.

Nesta tarefa iremos substituir diversas imagens geradas pelo Visual Studio para identificá-la como um Cardápio Eletrônico. Aproveitando o momento, iremos trocar todas as imagens PNGs na pasta Assets, justamente para customizar a aplicação, e ao final alterar o arquivo de manifesto.

  1. O primeiro passo é desinstalar a aplicação do Windows 8. Para isto, na página inicial do Windows 8, clique com o botão direito do mouse sobre o Tile do ContosoCookbook (ou se estiver usando o dedo, toque e arraste o dedo para baixo) e selecione Desinstalar (veja a Figura 9). Na mensagem de confirmação exibida, clique novamente em Desinstalar. Isto irá desinstalar a aplicação e remover o Tile.

Dn205307.ABCF5EB0D058FD40B31EF3C76FC92025(pt-br,MSDN.10).png

Figura 9 – Desinstalar a aplicação

  1. Retorne ao Visual Studio e clique com o botão direito na pasta Assets. Em seguida, selecione o comando Add - Existing Item para importar as imagens Logo.png, SmallLogo.png, SplashScreen.png, StoreLogo.png e WideLogo.png da pasta que contém os materiais destes exercícios. Uma alternativa mais comum e rápida (veja a Figura 10) é simplesmente arrastar todos os arquivos do Windows Explorer para a pasta Assets no Visual Studio. Como estes arquivos já existem, será exibida uma mensagem informando que os arquivos já existem e se deseja substitui-los. Selecione Yes para todos e pronto, já temos novas imagens.

Dn205307.7AECE4FBD26B7766095E844F11D4CE3E(pt-br,MSDN.10).png

Figura 10 – Cópia de arquivos

  1. No Solution Explorer, dê um duplo clique no arquivo Package.appxmanifest para abrir o manifesto da aplicação.
Dn205307.note(pt-br,MSDN.10).gifNota:
O manifesto da aplicação contém todos os metadados referentes ao estilo Modern UI, e isto vale para cada aplicação que você criar. Em tempo de execução, o manifesto informa ao Windows 8 absolutamente tudo o que é necessário sobre a aplicação, incluindo o nome, quem publicou, as capacidades requeridas pela aplicação, incluindo acesso a webcams, microfones, internet e partes do sistema de acesso aos arquivos, especialmente as pastas do usuário como as bibliotecas de documentos, músicas e videos.

  1. Nesta janela aberta, troque o nome (Display name) da aplicação para “Cardápio Eletrônico” e a descrição (Description) para “Aplicação de Cardápio Eletrônico”, como a Figura 11. Ainda nesta tela, note que não há nenhuma imagem na opção Wide logo, portanto, clique no botão selecionar (…) e aponte para “Assets\WideLogo.png”. Esta é a imagem será a opção de mostrar um logotipo em wide screen (tamanho largo) na janela inicial do Windows 8. O melhor de tudo é que o usuário que define isto, ou seja, você fornece as duas imagens e ele decide qual irá usar.

Dn205307.0AB80036394A9C9528FBAA4E12784BE8(pt-br,MSDN.10).png

Figura 11 – Trocar o nome da aplicação e configurar a imagem no manifesto

  1. Pressione F5 para executar a aplicação.
  2. Observe a tela inicial que mostrará rapidamente uma nova imagem SplashScreen.png atribuída no manifesto. Esta imagem estava com o que o Visual Studio gerou como padrão, porém, como substituimos todas as imagens, agora estão personalizadas.
  3. Pressione a tecla com o símbolo do Windows para ir à janela inicial e certifique-se que existe uma nova imagem no Tile, conforme a Figura 12.

Dn205307.73A313DBFFBDF92487F891AEA7E49CE9(pt-br,MSDN.10).png

Figura 12 – Novo ícone da aplicação

Dn205307.note(pt-br,MSDN.10).gifNota:
Se você preferi um Tile quadrado, dê um clique com o botão direito sobre a imagem larga (se for um monitor de toque ou um tablet, clique e arraste o dedo para baixo) e selecione a opção menor na barra de aplicação (veja a Figura 13).

Dn205307.CB39973D9F05735A6271DF5F88954BAD(pt-br,MSDN.10).png

Figura 13 – Alterar o tamanho do ícone da aplicação

  1. Retorne ao Visual Studio e pare a execução da aplicação.

Exercício 2: Carregar os Dados

Este projeto que foi criado baseado no modelo de Grid App já inclui um exemplo de dados, mas vamos fazer umas alterações para a nossa base de dados. A idéia é aproveitar toda a estrutura de acesso a dados que já vem montada no modelo e alterarmos para a nossa classe de receita de dados.

Tarefa 1 – Importar as Classes de Receita de Dados

O primeiro passo é trocar a classe de acesso a dados de exemplo do Visual Studio pela classe de receita de dados. Para ilustrar melhor, esta classe é de receita de pratos com os devidos grupos de culinária.

  1. Para adicionar um arquivo existente, no Solution Explorer, dê um clique com o botão direito sobre a pasta DataModel e selecione o Add - Existing Item. Na pasta com os exercícios base chamada Assets, localize a pasta Data e selecione o arquivo RecipeDataSource.cs e clique em OK. Outro meio mais rápido é a partir do Windows Explorer, pasta Assets/Data selecionar e arrastar o arquivo RecipeDataSource.cs para a pasta DataModel no Solution Explorer do projeto. Veja na Figura 14 a origem e o destino do arquivo.

Dn205307.014E21A48A1373E0A29C80BBC926C3F8(pt-br,MSDN.10).png

Figura 14 – Classe de dados com as receitas

Dn205307.note(pt-br,MSDN.10).gifNota:
O Visual Studio forneceu um arquivo chamado SampleDataSource.cs que contém as classes de dados chamadas SampleDataCommon, SampleDataItem, SampleDataGroup e SampleDataSource. O arquivo RecipeDataSource.cs contém uma versão destas mesmas classes de dados adaptadas para os dados das receitas, sendo: RecipeDataCommon, RecipeDataItem, RecipeDataGroup e RecipeDataSource. Diferente do SampleDataSource que inclui dados de exemplo no código, o RecipeDataSource contém métodos chamados LoadLocalDataAsync e LoadRemoteDataAsync que carregam os dados de um arquivo texto (iremos adicioná-lo ao projeto logo a seguir) ou diretamente do Azure. Cabe ressaltar que também está incluído tudo o que é necessário do Windows.Data.Json para fazer o Parser do formato JSON dos dados das receitas e carregá-los nas instâncias das classes RecipeDataItem e RecipeDataGroup. Fique à vontade para visualizar e entender este código que carrega os dados. Particularmente, veja o método LoadRemoteDataAsync que usa a classe HttpClient do .NET para carregar os dados oriundos da nuvem (Azure).

  1. Abra o arquivo RecipeDataSource.cs e vou destacar os pontos chave do código. O primeiro destaque é a assinatura da classe RecipeDataCommon o qual herda a classe BindableBase.
{
public abstract class RecipeDataCommon : ContosoCookbook.Common.BindableBase
}

Veja na Figura 15 o diagrama das duas classes para um melhor entendimento. Note que em RecipeDataCommon há diversas propriedades da receita (Image, ImagePath, ShortTitle, Title e UniqueId). Esta estrutura de classe servirá como base para preencher uma coleção de receitas oriundas do arquivo texto ou do Azure. No entanto, por que existe a herança de BindableBase? Primeiro que a maneira de vincular uma fonte de dados à interface de usuário (UI) é através do conceito chamado Binding, o qual veremos futuramente. Segundo que qualquer alteração que houver tanto na fonte quanto na UI, é preciso que as duas fiquem sincronizadas. Para isto, é preciso disparar um alerta informando que uma propriedade foi alterada tanto na fonte ou na UI. A este recurso de Binding é preciso implementar para cada propriedade de qualquer classe de dados a interface chamada INotifyPropertyChanged. Para facilitar e muito o trabalho dos desenvolvedores, o template já criou a classe BindlableBase que implementa a interface INotifyPropertyChanged.

Dn205307.398F8F2098F5BFAC11C58F7F9761A131(pt-br,MSDN.10).png

Figura 15 – Classe RecipeDataCommon e BindableBase

Como exemplo, vamos analisar o título (propriedade Title) da receita, o qual há o get (retorna o texto em si) e o set (atribui o texto). No set, onde o texto é atribuído à propriedade, é preciso verificar se houve uma alteração ou não, e neste caso quem faz isto é o método SetProperty que está na classe BindableBase. Imagine repetir esta verificação para cada propriedade de todas as classes, não faz o menor sentido, por isto que criamos classes e métodos de ajuda.

Dn205307.note(pt-br,MSDN.10).gifNota:
Para se deslocar rapidamente para uma classe ou método, clique no nome do método e pressione F12 ou clique com o botão direito e selecione Go To Definition.

private string _title = string.Empty;
public string Title
{
    get { return this._title; }
    set { this.SetProperty(ref this._title, value); }
}

Veja o método SetProperty que recebe por referência a proriedade e verifica se houve ou não alteração. Note que o retorno sempre será true ou false.

protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
    if (object.Equals(storage, value)) return false;

    storage = value;
    this.OnPropertyChanged(propertyName);
    return true;
}

Agora veja como é a assinatura da classe RecipeDataItem. Note que ela herda a classe RecipeDataCommon. O foco aqui não é explicar o que programação orientada à objetos, mas a dica é se você precisar criar uma classe onde alguma outra já contenha as propriedades, então basta herdá-la.

public class RecipeDataItem : RecipeDataCommon

Veja na Figura 16 que a classe RecipeDataItem contém as mesmas propriedades de RecipeDataCommon e mais as propriedades Directions, Group, Ingredients, PrepTime, TileImage e TileImagePath.

Dn205307.2691D88F4406A34F3F71C9F72AF7A448(pt-br,MSDN.10).png

Figura 16 – Classe RecipeDataItem

Um ponto interessante em relação a propriedade Ingredients. Quando se faz uma receita, um bolo por exemplo, é preciso saber quais são os ingredientes. Claro que a quantidade de ingredientes varia de acordo com a receita, portanto, nada melhor que atribuir que o tipo de dado será uma coleção de textos. Neste caso, quando falamos no .NET que será uma coleção de elementos, dizemos que será do tipo ObservableCollection. Isto porque se for preciso adicionar, excluir, alterar ou limpar a coleção, o próprio .NET framework já tem tudo pronto.

private ObservableCollection<string> _ingredients;
public ObservableCollection<string> Ingredients
{
    get { return this._ingredients; }
    set { this.SetProperty(ref this._ingredients, value); }
}

Outra propriedade importante de se entender é o Group (grupo) o qual pertence uma receita. Veja que ela é do tipo RecipeDataGroup.

private RecipeDataGroup _group;
public RecipeDataGroup Group
{
    get { return this._group; }
    set { this.SetProperty(ref this._group, value); }
}

A Figura 17 ilustra toda a classe RecipeDataGroup que contém as propriedades do grupo em si, a descrição, a imagem do grupo, os itens, a quantidade de receitas e as melhores receitas.

Dn205307.DEAD13189135E20B07BEAEA09D50BFF0(pt-br,MSDN.10).png

A Figura 17 ilustra toda a classe RecipeDataGroup que contém as propriedades do grupo em si, a descrição, a imagem do grupo, os itens, a quantidade de receitas e as melhores receitas.

public IEnumerable<RecipeDataItem> TopItems
{
    get { return this._items.Take(12); }
}

public int RecipesCount
{
    get
    {
        return this.Items.Count; 
    } 
}

Agora preciso analisar a principal classe que usaremos o tempo todo, é a RecipeDataSource, o qual gerencia todos os dados das receitas e grupos. Na Figura 18 vemos a propriedade AllGroups e todos os métodos.

Dn205307.FB4413C25559DD553B1C78BDB5BCEB6E(pt-br,MSDN.10).png

Figura 18 – Classe RecipeDataSource

Esta classe define uma propriedade chamada AllGroups do tipo ObservableCollection, ou seja, representa uma coleção de dados dos grupos das receitas. O método GetGroups recebe como parâmetro o código (uniqueId) do grupo e retorna a propriedade AllGroups. Note que o tipo é IEnumerable de RecipeDataGroup, ou seja, é a lista de grupos. O uso do IEnumerable significa que é feita uma leitura em toda a lista de dados apenas uma única vez. Você não consegue reler esta lista devido a característica do IEnumerable. Se ajudar fazer uma relação com o ADO.NET, é como se usasse o DataReader, é forward-only.

public sealed class RecipeDataSource
{
    //public event EventHandler RecipesLoaded;

    private static RecipeDataSource _recipeDataSource = new RecipeDataSource();
        
    private ObservableCollection<RecipeDataGroup> _allGroups = new ObservableCollection<RecipeDataGroup>();
    public ObservableCollection<RecipeDataGroup> AllGroups
    {
       get { return this._allGroups; }
    }

    public static IEnumerable<RecipeDataGroup> GetGroups(string uniqueId)
    {
        if (!uniqueId.Equals("AllGroups")) throw new ArgumentException("Only 'AllGroups' is supported as a collection of groups");

        return _recipeDataSource.AllGroups;
}

O método GetGroup recebe o código e retorna os dados do grupo em si. Note que o tipo de retorno na assinatura do método é RecipeDataGroup. Mas como é feita a pesquisa? É usada uma expressão Lambda com o método de extensão Where onde a pesquisa é feita na propriedade AllGroups. O filtro a ser usado é o código do grupo (uniqueId) onde o critério é especificado em group.UniqueId.Equals(uniqueId), ou seja, localize em AllGroups o grupo cujo UniqueId seja igual ao parâmetro uniqueId passado na assinatura do método.

Se encontrar, neste caso se o contador for igual a 1, retorna o primeiro item da lista, que neste caso é o grupo localizado. Caso contrário, retorna nulo. Particularmente, sei que há outra forma de realizar esta consulta com o FirstOrDefault, mas vamos usar o que está escrito.

public static RecipeDataGroup GetGroup(string uniqueId)
{
    // Simple linear search is acceptable for small data sets
    var matches = _recipeDataSource.AllGroups.Where((group) => group.UniqueId.Equals(uniqueId));
    if (matches.Count() == 1) return matches.First();
    return null;
}

Como obter todos os dados de uma única receita, ou ainda, o objeto RecipeDataItem (a receita em si)? O método GetItem é do tipo RecipeDataItem e recebe como parâmetro o código da receita (uniqueId). Primeiro a pesquisa é feita em todos os itens dos grupos com o SelectMany para selecionar todos os itens do grupo. Em seguida é usado o Where para filtrar apenas um item cujo UniqueId for igual (Equals) ao código passado como parâmetro no uniqueId da assinatura do método.

Em seguida, o Count verifica se é igual a 1 e retorna o primeiro elemento. Caso contrário, retorna nulo.

public static RecipeDataItem GetItem(string uniqueId)
{
    // Simple linear search is acceptable for small data sets
    var matches = _recipeDataSource.AllGroups.SelectMany(group => group.Items).Where((item) => item.UniqueId.Equals(uniqueId));
    if (matches.Count() == 1) return matches.First();
    return null;
}

A seguir temos um dos principais métodos de todo o projeto. Primeiro, este método é assinado com a palavra chave ASYNC que significa que será um método assincrono, ou seja, ele poderá criar e executar diversas threads (tarefas) ao mesmo tempo, simultaneamente. Segundo, outra novidade é que o método é do tipo Task, e para o .NET framework 4.5, uma Task é uma tarefa a ser gerenciada, não importa o conteúdo.

Este código utiliza o HttpClient porque irá trazer dados da nuvem (Azure) através da rede, é claro. O método GetAsync se encarrega de conectar no endereço declarado na URI o qual retorna um conjunto de dados via Data Services (OData ou Web API). A variável result que é do tipo var (neste caso entenda-se String), irá receber a leitura dos dados da rede. Uma vez tendo todos os dados é preciso fazer o que chamamos de Parse do Json, ou seja, o JsonArray se encarrega de deixar os dados inteligíveis da forma que as classes das receitas (definidas anteriormente) entendam. Para isto, existe a chamada do método CreateRecipesAndRecipeGroups passando o Json como parâmetro. Todas as execuções assinadas com o await significam que serão executadas assincronas, simultaneamente.

public static async Task LoadRemoteDataAsync()
{
    // Retrieve recipe data from Azure
    var client = new HttpClient();
    client.MaxResponseContentBufferSize = 1024 * 1024; // Read up to 1 MB of data
    var response = await client.GetAsync(new Uri("http://contosorecipes8.blob.core.windows.net/AzureRecipesRP"));
    var result = await response.Content.ReadAsStringAsync();

    // Parse the JSON recipe data
    var recipes = JsonArray.Parse(result);

    // Convert the JSON objects into RecipeDataItems and RecipeDataGroups
    CreateRecipesAndRecipeGroups(recipes);
}

Para se ter uma idéia de como os dados retornam no formato Json, abra o Internet Explorer e cole esta URL http://contosorecipes8.blob.core.windows.net/AzureRecipesRP na barra de endereço. Veja na Figura 19 como que os dados são retornados.

Dn205307.7903646D1387356E3762436AE7C60275(pt-br,MSDN.10).png

Figura 19 – Dados do Azure

Basicamente este método recebe como parâmetro um array do tipo JsonArray, monta um looping para varrer todos os itens da lista, e a cada item verifica qual o tipo para adicionar à lista de item (RecipeDataItem) ou grupo (RecipeDataGroup).

private static void CreateRecipesAndRecipeGroups(JsonArray array)
{
    foreach (var item in array)
    {
        var obj = item.GetObject();
        RecipeDataItem recipe = new RecipeDataItem();
        RecipeDataGroup group = null;

        foreach (var key in obj.Keys)
        {
            IJsonValue val;
            if (!obj.TryGetValue(key, out val))
                continue;

            switch (key)
            {
                case "key":
                    recipe.UniqueId = val.GetNumber().ToString();
                    break;
                case "title":
                    recipe.Title = val.GetString();
                    break;
                case "shortTitle":
                    recipe.ShortTitle = val.GetString();
                    break;
                case "preptime":
                    recipe.PrepTime = (int)val.GetNumber();
                    break;
                case "directions":
                    recipe.Directions = val.GetString();
                    break;
                case "ingredients":
                    var ingredients = val.GetArray();
                    var list = (from i in ingredients select i.GetString()).ToList();
                    recipe.Ingredients = new ObservableCollection<string>(list);
                    break;
                case "backgroundImage":
                    recipe.SetImage(val.GetString());
                    break;
                case "tileImage":
                    recipe.SetTileImage(val.GetString());
                    break;
                case "group":
                    var recipeGroup = val.GetObject();

                    IJsonValue groupKey;
                    if (!recipeGroup.TryGetValue("key", out groupKey))
                        continue;

                    group = _recipeDataSource.AllGroups.FirstOrDefault(c => c.UniqueId.Equals(groupKey.GetString()));

                    if (group == null)
                        group = CreateRecipeGroup(recipeGroup);

                    recipe.Group = group;
                    break;
            }
        }

        if (group != null)
            group.Items.Add(recipe);
    }
}

Como trabalhar com dados localmente? O primeiro passo é criar uma pasta no projeto chamada Data. Para isto, no Solution Explorer, clique com o botão direito sobre o projeto ContosoCookbook, selecione Add / New Folder e digite Data. A partir do material de base para os exercícios, na pasta Assets/Data, selecione o arquivo Recipes.txt e arraste-o para a pasta criada chamada Data no Solution Explorer. Veja na Figura 20 o arquivo para referência. Abra-o e veja que a estrutura é a mesma do arquivo que vem do Azure, todo texto.

Dn205307.277238788064806A844DDA4ED368F1E1(pt-br,MSDN.10).png

Figura 20 – Dados locais do arquivo Recipes.txt

Agora, novamente no arquivo RecipeDataSource.cs, localize o método LoadLocalDataAsync. Veja que a assinatura também é assíncrona com o ASYNC e é do tipo Task. Para abrir o arquivo Recipes.txt é usado o GetFileAsync e para efetivar a leitura é usado o ReadTextAsync. No entanto, observe que ambos usam o await, pois serão executados assíncronos. Uma vez tendo os dados, é feito o parse do Json e chamado o mesmo método do anterior para organizar as informações de itens e grupos.

public static async Task LoadLocalDataAsync()
{
    // Retrieve recipe data from Recipes.txt
    var file = await Package.Current.InstalledLocation.GetFileAsync("Data\\Recipes.txt");
    var result = await FileIO.ReadTextAsync(file);

    // Parse the JSON recipe data
    var recipes = JsonArray.Parse(result);

    // Convert the JSON objects into RecipeDataItems and RecipeDataGroups
    CreateRecipesAndRecipeGroups(recipes);
}
  1. O próximo passo é ajustar todas as referências (destacadas em amarelo) da classe SampleDataSource para RecipeDataSource, de SampleDataGroup para RecipeDataGroup e de SampleDataItem para RecipeDataItem. Para isto, abra os arquivos GroupdedItemsPage.xaml.cs, GroupDetailPage.xaml.cs e ItemDetailPage.xaml.cs. Estes ajustes são fundamentais para a correta execução do projeto, afinal, temos novas classes.
DE:

protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter);
    this.DefaultViewModel["Groups"] = sampleDataGroups;
}

PARA:

protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    var sampleDataGroups = RecipeDataSource.GetGroups((String)navigationParameter);
    this.DefaultViewModel["Groups"] = sampleDataGroups;
}

DE:

void Header_Click(object sender, RoutedEventArgs e)
{
    var group = (sender as FrameworkElement).DataContext;
    this.Frame.Navigate(typeof(GroupDetailPage), ((SampleDataGroup)group).UniqueId);
}
PARA:

void Header_Click(object sender, RoutedEventArgs e)
{
    var group = (sender as FrameworkElement).DataContext;
    this.Frame.Navigate(typeof(GroupDetailPage), ((RecipeDataGroup)group).UniqueId);
}

DE:

void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
    var itemId = ((SampleDataItem)e.ClickedItem).UniqueId;
    this.Frame.Navigate(typeof(ItemDetailPage), itemId);
}

PARA:

void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
    var itemId = ((RecipeDataItem)e.ClickedItem).UniqueId;
    this.Frame.Navigate(typeof(ItemDetailPage), itemId);
}
DE:

protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    var group = SampleDataSource.GetGroup((String)navigationParameter);
    this.DefaultViewModel["Group"] = group;
    this.DefaultViewModel["Items"] = group.Items;
}

PARA:

protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    var group = RecipeDataSource.GetGroup((String)navigationParameter);
    this.DefaultViewModel["Group"] = group;
    this.DefaultViewModel["Items"] = group.Items;
}

DE:

void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
    var itemId = ((SampleDataItem)e.ClickedItem).UniqueId;
    this.Frame.Navigate(typeof(ItemDetailPage), itemId);
}

PARA:

void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
    var itemId = ((RecipeDataItem)e.ClickedItem).UniqueId;
    this.Frame.Navigate(typeof(ItemDetailPage), itemId);
}
DE:

protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    if (pageState != null && pageState.ContainsKey("SelectedItem"))
    {
        navigationParameter = pageState["SelectedItem"];
    }
    var item = SampleDataSource.GetItem((String)navigationParameter);
    this.DefaultViewModel["Group"] = item.Group;
    this.DefaultViewModel["Items"] = item.Group.Items;
    this.flipView.SelectedItem = item;
}

PARA:

protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    if (pageState != null && pageState.ContainsKey("SelectedItem"))
    {
        navigationParameter = pageState["SelectedItem"];
    }
    var item = RecipeDataSource.GetItem((String)navigationParameter);
    this.DefaultViewModel["Group"] = item.Group;
    this.DefaultViewModel["Items"] = item.Group.Items;
    this.flipView.SelectedItem = item;
}

DE:

protected override void SaveState(Dictionary<String, Object> pageState)
{
    var selectedItem = (SampleDataItem)this.flipView.SelectedItem;
    pageState["SelectedItem"] = selectedItem.UniqueId;
}

PARA:

protected override void SaveState(Dictionary<String, Object> pageState)
{
    var selectedItem = (RecipeDataItem)this.flipView.SelectedItem;
    pageState["SelectedItem"] = selectedItem.UniqueId;
}

Tarefa 2 – Carregar Imagens das Receitas

Como trazemos dados do Azure ou usamos localmente, cada receita possui uma imagem do prato em si, assim como cada grupo de receitas contém a imagem do grupo. Portanto, veja como preparar o projeto com todas as imagens do grupo e das receitas.

  1. No Solution Explorer, clique com o botão direito no nome do projeto ContosoCookbook, selecione Add / New Folder e digite “Images”.
  2. No material de base destes exercícios, localize a pasta Assets/Images, conforme a Figura 21. Nesta pasta selecione as pastas chamadas: chinese, french, german, indian, italian e mexican. Arraste-as sobre a pasta Images criada no Projeto. Isto irá adicionar todas as imagens para cada uma das receitas e do grupo.
Dn205307.note(pt-br,MSDN.10).gifNota:
Certifique-se que o nome da pasta criada no projeto chama-se Images. Isto porque todos os itens contidos no arquivo Recipes.txt assumem exatamente estes endereços nas URLs das imagens.

Dn205307.7FEC3C21833463010533FE8A86930911(pt-br,MSDN.10).png

Figura 21 – Imagens das receitas

Tarefa 3 – Carregar as Receitas

Agora vamos colocar a mão no código literalmente. O objetivo é criar os devidos códigos em C# para carregar os dados com os grupos e as receitas com todos os dados.

  1. Abra o arquivo App.xaml.cs e adicione o seguinte namespace na lista de using no topo do arquivo. Isto fará com que tenhamos acesso a todo e qualquer classe deste namespace.
using ContosoCookbook.Data;
  1. Em seguida, localize o método OnLaunched. Adicione o seguinte código destacado em amarelo para chamar o método LoadLocalDataAsync da classe RecipeDataSource. Isto irá carregar os dados descritos no arquivo Recipes.txt. Note que esta chamada será assíncrona devido ao uso do await, ou seja, esta tarefa será realizada paralelamente a outra. Mas, isto ocorrerá somente se houver necessidade de processamento, de qualquer forma o código já fica pronto. Outra vantagem é que enquanto o carregamento dos dados ocorrerem em paralelo, a UI não será travada, ou seja, o usuário pode manusear a UI enquanto outros dados são carregados.
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
    // Do not repeat app initialization when already running, just ensure that
    // the window is active
    if (args.PreviousExecutionState == ApplicationExecutionState.Running)
    {
        Window.Current.Activate();
        return;
    }

    // Carrega os dados das receitas
    await RecipeDataSource.LoadLocalDataAsync();

    // Create a Frame to act as the navigation context and associate it with
    // a SuspensionManager key
    var rootFrame = new Frame();
    SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

O código anterior carrega os dados localmente. No entanto, caso queira executar uma chamada diretamente no Azure, adicione o seguinte código. Mas, lembre-se que este código usa uma URL que a Microsoft disponibilizou para exercícios. No momento em que ela retirar do ar ou alterar o endereço, o seu aplicativo não irá funcionar. Atenção que toda requisição no Azure você precisa obrigatoriamente estar conectado à internet.

protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
    // Do not repeat app initialization when already running, just ensure that
    // the window is active
    if (args.PreviousExecutionState == ApplicationExecutionState.Running)
    {
        Window.Current.Activate();
        return;
    }

    // Carrega os dados do Azure
    await RecipeDataSource.LoadRemoteDataAsync();

Cabe ressaltar que para efeito de exercício inicial coloque um comentário nesta linha do Azure, ou seja, vamos trabalhar com dados localmente. Um último ajuste para personalizar a tela inicial é o texto no título. Abra o arquivo App.xaml e altere a chave AppName para Cardápio Eletrônico.

<x:String x:Key="AppName">Cardápio Eletrônico</x:String>

Tarefa 4 – Testar a Aplicação

Depois de tantos ajustes, chegou a hora de executar a aplicação e visualizar as alterações.

  1. Pressione F5 para executar e veja se a aplicação mostra uma imagem como a Figura 22. Veja que com poucas alterações e os devidos ajustes na aplicação podemos ter uma aplicação completa, isto é sensacional!

Dn205307.9DCBC0D6E8E922CC825978385F1FCE3B(pt-br,MSDN.10).png

Figura 22 – Tela inicial com as receitas

  1. Retorne ao Visual Studio e pare a execução (SHIFT + F5).

Exercício 3: Customizar a UI

Podemos assumir que a aplicação está rodando adequadamente, porém em relação ao layout é preciso realizar diversas melhorias. Como usamos uma classe de dados contendo informações sobre as receitas, nada melhor que explorar todo o conteúdo das receitas, por exemplo, mostrar o modo de preparo e os ingredientes. Neste exercício, você irá modificar a página inicial, a página de detalhes e a de grupo para refinar a exibição do Cardápio Eletrônico.

Tarefa 1 – Modificar a Página Inicial

Vamos iniciar modificando o layout da página incial com os itens do cardápio.

  1. Na pasta Common do projeto, abra o arquivo StandardStyles.xaml. Este arquivo é de extrema importância para o entendimento de todo o layout. Toda e qualquer configuração sobre o layout dos dados estão definidos neste arquivo. A vantagem é que você pode alterar qualquer layout e usá-lo em todas as páginas da aplicação. Entenda como um repositório de layouts reaproveitáveis, afinal a estrutura é XAML.
  2. Localize o elemento do DataTemplate chamado “Standard250x250ItemTemplate”. O meio mais fácil é usar o CTRL + F e localizar esta expressão. Este template é usado para renderizar os itens do cardápio na página inicial. De acordo com a listagem a seguir, altere a largura (Width) para 320 e a altura (Height) para 240 do Grid, preservando assim o aspecto das imagens da receita. Os detalhes estão destacados em amarelo na listagem a seguir.
  3. No primeiro TextBlock que faz o Binding para a propriedade Title, altere para ShortTitle, afinal não há a propriedade Title, e sim ShortTitle. Ainda neste controle, altere a altura (Height) de 60 para 48, diminuindo a altura do controle que contém uma parte transparente no rodapé de cada item.
  4. Em seguida, remova o TextBlock que está em amarelo onde o Binding aponta para Subtitle, afinal a classe RecipeDataItem não contém uma propriedade chamada Subtitle.
DE:

<DataTemplate x:Key="Standard250x250ItemTemplate">
    <Grid HorizontalAlignment="Left" Width="250" Height="250">
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
            <Image Source="{Binding Image}" Stretch="UniformToFill"/>
        </Border>
        <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
            <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0"/>
            <TextBlock Text="{Binding Subtitle}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/>
        </StackPanel>
    </Grid>
</DataTemplate>

PARA:

<DataTemplate x:Key="Standard250x250ItemTemplate">
    <Grid HorizontalAlignment="Left" Width="320" Height="240">
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
            <Image Source="{Binding Image}" Stretch="UniformToFill"/>
        </Border>
        <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
            <TextBlock Text="{Binding ShortTitle}" 
                       Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" 
                       Style="{StaticResource TitleTextStyle}" 
                       Height="48" Margin="15,0,15,0"/>
        </StackPanel>
    </Grid>
</DataTemplate>
  1. Salve o arquivo (CTRL + S) e pressione F5 para executar a aplicação. Observe que os itens do cardápio estão com um novo visual na página inicial, conforme a Figura 23.

Dn205307.0E0F926CD1396CDA3A6A25C401CDFD27(pt-br,MSDN.10).png

Figura 23 – Novo layout da tela inicial

  1. Retorne ao Visual Studio e pare a execução.

Tarefa 2 – Modificar a Página de Detalhes do Grupo

Na página inicial são exibidos diversos grupos de comidas, por exemplo Chinese, Italian, French, etc. No entanto, o layout apresentado está incompleto, precisamos adequar a classe de dados do grupos que criamos.

  1. Execute a aplicação novamente e selecione o grupo “Chinese” na tela de navegação. Isto levará à página de detalhes do grupo com todas as receitas deste grupo, conforme a Figura 24. Precisamos customizar o espaço entre o título “Chinese” e a imagem do grupo, assim como as informações das receitas, mostrando o tempo de preparo de cada item.

Dn205307.F49748BB8E9F7EDF9969993280152126(pt-br,MSDN.10).png

Figura 24 – Layout da página de detalhes do grupo

  1. Retorne ao Visual Studio e pare a execução. Abra o arquivo GroupDetailPage.xaml e localize o elemento <GridView.Header>. Remova o primero TextBlock destacado em amarelo na listagem a seguir. No elemento Image na próxima linha, altere o Height=”400” para Width=”480” e o topo da margem de 0 para 10.
DE:

<GridView.Header>
    <StackPanel Width="480" Margin="0,4,14,0">
        <TextBlock Text="{Binding Subtitle}" Margin="0,0,18,20" 
                    Style="{StaticResource SubheaderTextStyle}" MaxHeight="60"/>
        <Image Source="{Binding Image}" Height="400" Margin="0,0,18,20" 
                Stretch="UniformToFill"/>
        <TextBlock Text="{Binding Description}" Margin="0,0,18,0" 
                    Style="{StaticResource BodyTextStyle}"/>
    </StackPanel>
</GridView.Header>

PARA:

<GridView.Header>
    <StackPanel Width="480" Margin="0,4,14,0">
        <Image Source="{Binding Image}" Width="480" Margin="0,10,18,20" 
                Stretch="UniformToFill"/>
        <TextBlock Text="{Binding Description}" Margin="0,0,18,0" 
                    Style="{StaticResource BodyTextStyle}"/>
    </StackPanel>
</GridView.Header>
  1. Em seguida, retorne para o arquivo StandardStyles.xaml e localize o elemento DataTemplate chamado “Standard500x130ItemTemplate”. Este template renderiza os itens das receitas da página de itens do grupo. Altere a largura (Width) do Grid de 480 para 360.
  2. Localize o elemento Border e remova o atributo Width=”110” para respeitar as dimensões das imagens das receitas.
  3. Localize e remova os dois TextBlocks cuja propriedade Text tem o Binding para Subtitle e Description, afinal não temos estas propriedades na fonte de dados.
  4. No TextBlock restante, localize a propriedade Text e altere o Binding de Title para ShortTitle.
  5. Logo a seguir deste TextBlock, adicione um elemento do tipo StackPanel contendo três TextBlocks, sendo que o segundo faz o Binding para a propriedade PrepTime (tempo de preparo em minutos).
DE:

<DataTemplate x:Key="Standard500x130ItemTemplate">
    <Grid Height="110" Width="480" Margin="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}" Width="110" Height="110">
            <Image Source="{Binding Image}" Stretch="UniformToFill"/>
        </Border>
        <StackPanel Grid.Column="1" VerticalAlignment="Top" Margin="10,0,0,0">
            <TextBlock Text="{Binding Title}" Style="{StaticResource TitleTextStyle}" TextWrapping="NoWrap"/>
            <TextBlock Text="{Binding Subtitle}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap"/>
            <TextBlock Text="{Binding Description}" Style="{StaticResource BodyTextStyle}" MaxHeight="60"/>
        </StackPanel>
    </Grid>
</DataTemplate>

PARA:

<DataTemplate x:Key="Standard500x130ItemTemplate">
    <Grid Height="110" Width="360" Margin="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}" Height="110">
            <Image Source="{Binding Image}" Stretch="UniformToFill"/>
        </Border>
        <StackPanel Grid.Column="1" VerticalAlignment="Top" Margin="10,0,0,0">
            <TextBlock Text="{Binding ShortTitle}" 
                       Style="{StaticResource TitleTextStyle}" 
                       TextWrapping="NoWrap"/>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Tempo de preparo:" Style="{StaticResource BodyTextStyle}" />
                <TextBlock Text="{Binding PrepTime}" Style="{StaticResource BodyTextStyle}" Margin="4,0,4,0" />
                <TextBlock Text="minutos" Style="{StaticResource BodyTextStyle}" />
            </StackPanel>
        </StackPanel>
    </Grid>
</DataTemplate>
  1. Execute a aplicação novamente (F5), selecione o grupo Chinese e veja se o layout parece com a imagem da Figura 25. Note que a cada item do cardápio é exibido o tempo de preparo da receita.

Dn205307.CBA5C76088842F8DDB4803B30E3E7AA1(pt-br,MSDN.10).png

Figura 25 – Novo layout da página de detalhes do grupo

  1. Retorne ao Visual Studio e pare a execução.

Os Bastidores do Layout

Um ponto importante para quem nunca trabalhou com XAML é entender como ocorre o vínculo de controles como GridView, ListView e FlipView com o layout definido do DataTemplate do arquivo StandardStyles.xaml na pasta Common. Veja o exemplo do GridView contido na página GroupDetailPage.xaml. Observe que o atributo ItemTemplate usa um recurso (StaticResource) que aponta para uma chave chamada Standard500x130ItemTemplate. Aqui é o segredo do entendimento, pois se você analisar todos os DataTemplates no StandardStyles.xaml notará que cada um contém uma chave (x:Key=”NomeDaChave”). Sendo assim, todo e qualquer controle que suportar esta estrutura de DataTemplate, permite usar este layout para exibir as informações. Este conceito é idêntico para qualquer controle, por exemplo um ListBox, Combox, etc. Já a estutura interna do DataTemplate você pode usar qualquer elemento do XAML, depende da sua criatividade.

Uma curiosidade sobre o DataTemplate. Este nome se dá justamente porque irá exibir dados de uma fonte de dados, seja uma coleção, uma classe, um XML, um TXT, etc. Então, quando pesquisar sobre a forma de dispor os dados em um controle, tenha certeza que se o controle suportar o DataTemplate é porque tudo fica mais fácil de se implementar.

Vínculo do XAML com o template no StandardStyles.xaml

<GridView
    x:Name="itemGridView"
    ...
    ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
    ItemTemplate="{StaticResource Standard500x130ItemTemplate}"
    SelectionMode="None"
    IsItemClickEnabled="True"
    ItemClick="ItemView_ItemClick">
<DataTemplate x:Key="Standard500x130ItemTemplate">
    <Grid Height="110" Width="360" Margin="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Layout conforme a necessidade ...>
    </Grid>
</DataTemplate

Tarefa 3 – Modificar a Página de Detalhes da Receita

Atualmente temos uma coleção de dados que contém todas as informações referentes ao cardápio, mas a página de detalhes da receita não exibe isto. Sendo assim, é preciso incluir os itens do modo de preparo e os ingredientes da receita.

  1. Execute a aplicação novamente e clique em qualquer receita do cardápio. Observe na Figura 26 que nem todos os dados desta receita são exibidos, então nos resta inclui-los no código.

Dn205307.5D5340F72B6C7D3CD24BABE8257BCA32(pt-br,MSDN.10).png

  1. Retorne ao Visual Studio e pare a execução. No Solution Explorer, dê um clique com o botão direito na pasta Common e selecione Add – Class. Nomeie como ListConverter.cs. É criada a classe assinada como class ListConverter apenas. No entanto, como ensinarei o conceito chamado Converter, é preciso herdar a classe IValueConverter. Portanto, veja a seguir a assinatura da classe:
class ListConverter : IValueConverter
{
}
  1. Neste ponto é mostrada uma linha pontilhada vermelha abaixo do IValueConverter, informando que o assembly não pode ser encontrado. Aqui vai a dica: selecione o IValueConverter, pressione CTRL + . (ponto). Na lista aberta é informado que será adicionado o namespace using Windows.UI.Xaml.Data; na lista de usings. Simplesmente dê um ENTER e note que este namespace será inserido na lista de usings.
  2. Logo em seguida o IValueConverter muda de cor, porém como é uma Interface de uma classe, você é avisado que é preciso implementar esta Interface. Para isto, pressione CTRL + . (ponto) novamente e ENTER. Magicamente teremos dois métodos, Convert e ConvertBack.
class ListConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}
  1. O conceito de Converter é fácil de entender. Se você precisar converter, formatar, atribuir propriedades para qualquer objeto, use o Converter, afinal ele é um conversor. O método Convert pode ser usado em qualquer atributo do XAML com o Binding. Já o ConvertBack é usado somente em casos onde o Binding declara o Mode=TwoWay, ou seja, caso houver uma alteração na propriedade, seja na fonte de dados ou no layout do XAML, o ConvertBack será disparado. Ambos os métodos recebem e retornam um Object, portanto, é comum o uso de Cast para conversão.
  2. Agora precisamos alterar o código do método Convert para atender a necessidade. Se você relembrar que os dados referentes aos Ingredientes é uma coleção (ObservableCollection) de textos (strings), o qual o retorno (Recipes.txt ou Azure) é um array de strings, então é preciso criar uma forma de ler todos os itens do array e adicionar uma quebra de linha a cada item. O código listado a seguir é a classe completa. Observe que o array passado como parâmetro é uma coleção, porém o método recebe um object, então é preciso fazer uma conversão (Cast) para o tipo ObservableCollection<string>. Em seguida, é montado um looping para percorrer todos os itens e a cada um, são adicionados os caracteres “\r\n” que são a quebra e o retorno de linha. Em seguida, cada item é adicionado à variável builder, é convertida para string (ToString()) e retornada a quem chamou.
  3. Quantas vezes o looping é disparado? A quantidade de ingredientes é sempre fixa? A resposta é simples. O uso do looping foreach é automática porque ele será executado de acordo com a quantidade de itens contidos na coleção chamada de items. Com isto, o código fica perfeito, sem necessidade de nenhuma manutenção.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Data;

namespace ContosoCookbook.Common
{
    class ListConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            ObservableCollection<string> items = (ObservableCollection<string>)value;
            StringBuilder builder = new StringBuilder();

            foreach (var item in items)
            {
                builder.Append(item);
                builder.Append("\r\n");
            }

            return builder.ToString();
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }
}
  1. Já que temos o Converter definido, como podemos utiliza-lo? Primeiro que o ListConverter é uma classe e como iremos referenciá-la no XAML, é obrigatório que você compile o programa. Para isto, pressione CTRL + SHIFT + B ou F6 ou selecione o menu Build / Build Solution. Observe na linha de rodapé se a mensagem exibida é “Build succeeded”, dizendo que a solução foi compilada com sucesso.
  2. Em seguida, abra o arquivo ItemDetailPage.xaml e adicione o seguinte código destacado em amarelo no bloco de recursos da página <Page.Resources> no topo do arquivo. Isto é para declarar a instância da classe ListConverter dentro do XAML. Quando você digitar o “common:” o ListConverter deverá aparecer automaticamente, pois é a classe que contém o namespace ContosoCookbook.Common.
<Page.Resources>
        <common:ListConverter x:Key="ListConverter" />
        
        <!-- Collection of items displayed by this page -->
        <CollectionViewSource
            x:Name="itemsViewSource"
            Source="{Binding Items}"
            d:Source="{Binding AllGroups[0].Items, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>
    </Page.Resources>
  1. Nesta mesma página localize o controle FlipView chamado “flipView”. Este controle é fantástico porque você informa no ItemSource qual é a fonte de dados. Neste caso, é uma coleção de receitas e o controle exibe todos os itens com setas de navegação para frente e para trás. No entanto, o template usado como padrão define todo o layout dentro do elemento DataTemplate. Como temos outras informações, localize e selecione todo o DataTemplate e exclua-o. Na listagem a seguir, está o código completo que você deverá substituir o DataTemplate. Este código é o layout que exibirá todos os dados do cardápio em si (título, imagem, modo de preparo, tempo de preparo), sendo um GridView com 3 colunas. O título, a imagem e o tempo de preparo aparecem na coluna 1, a lista de ingredientes na coluna 2 e o modo de preparo na coluna 3.
<FlipView.ItemTemplate>
    <DataTemplate>
        <UserControl Loaded="StartLayoutUpdates" Unloaded="StopLayoutUpdates">
            <ScrollViewer x:Name="scrollViewer" Style="{StaticResource VerticalScrollViewerStyle}" Grid.Row="1">

                <!-- Three-column grid for item-detail layout -->
                <Grid Margin="120,0,20,20">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="400" />
                        <ColumnDefinition Width="40" />
                        <ColumnDefinition Width="360" />
                        <ColumnDefinition Width="40" />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>

                    <StackPanel Orientation="Vertical" Grid.Column="0">
                        <TextBlock FontSize="26.667" FontWeight="Light" Text="{Binding Title}" TextWrapping="Wrap"/>
                        <Image x:Name="image" Width="400" Margin="0,20,0,10" Stretch="Uniform" Source="{Binding Image}"/>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock FontSize="26.667" FontWeight="Light" Text="Tempo de preparo:"/>
                            <TextBlock FontSize="26.667" FontWeight="Light" Text="{Binding PrepTime}" Margin="8,0,8,0"/>
                            <TextBlock FontSize="26.667" FontWeight="Light" Text="minutos"/>
                        </StackPanel>
                    </StackPanel>

                    <StackPanel Orientation="Vertical" Grid.Column="2">
                        <TextBlock FontSize="26.667" FontWeight="Light" Text="Ingredientes" Margin="0,0,0,16"/>
                        <TextBlock FontSize="20" FontWeight="Light" LineHeight="32.5" Text="{Binding Ingredients, Converter={StaticResource ListConverter}}" TextWrapping="Wrap" />
                    </StackPanel>

                    <StackPanel Orientation="Vertical" Grid.Column="4">
                        <TextBlock FontSize="26.667" FontWeight="Light" Text="Modo de preparo" Margin="0,0,0,16"/>
                        <ScrollViewer Style="{StaticResource VerticalScrollViewerStyle}">
                            <Grid>
                                <TextBlock FontSize="20" FontWeight="Light" Text="{Binding Directions}" TextWrapping="Wrap" />
                            </Grid>
                        </ScrollViewer>
                    </StackPanel>
                </Grid>

                <VisualStateManager.VisualStateGroups>
                    <!-- Visual states reflect the application's view state inside the FlipView -->
                    <VisualStateGroup x:Name="ApplicationViewStates">
                        <VisualState x:Name="FullScreenLandscape"/>
                        <VisualState x:Name="Filled"/>

                        <!-- Respect the narrower 100-pixel margin convention for portrait -->
                        <VisualState x:Name="FullScreenPortrait">
                            <Storyboard>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="image" Storyboard.TargetProperty="MaxHeight">
                                    <DiscreteObjectKeyFrame KeyTime="0" Value="400"/>
                                </ObjectAnimationUsingKeyFrames>
                            </Storyboard>
                        </VisualState>

                        <!-- When snapped, the content is reformatted and scrolls vertically -->
                        <VisualState x:Name="Snapped">
                            <Storyboard>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="scrollViewer" Storyboard.TargetProperty="Style">
                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource VerticalScrollViewerStyle}"/>
                                </ObjectAnimationUsingKeyFrames>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="image" Storyboard.TargetProperty="MaxHeight">
                                    <DiscreteObjectKeyFrame KeyTime="0" Value="160"/>
                                </ObjectAnimationUsingKeyFrames>
                            </Storyboard>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>
            </ScrollViewer>
        </UserControl>
    </DataTemplate>
</FlipView.ItemTemplate>
  1. Quero destacar o uso do ListConverter. Os ingredientes são retornados na forma de um array de strings. No atributo Text há o Binding para a propriedade Ingredients para vincular os dados. O uso do converter através da palavra chave Converter aponta para o recurso StaticResource com a chave ListConverter que foi definida em Page.Resources anteriormente. Sendo assim, toda vez que você encontrar a palavra chave Converter significa que há uma chamada para uma classe de converter.
<TextBlock FontSize="20" FontWeight="Light" LineHeight="32.5" 
Text="{Binding Ingredients, Converter={StaticResource ListConverter}}" 
TextWrapping="Wrap" />
  1. Agora execute a aplicação (F5). Selecione qualquer receita e veja o layout da página de detalhes da receita, segundo a Figura 27. Caso queira navegar entre as receitas deste grupo, basta clicar nas setas do controle FlipView. Cabe ressaltar que a resolução padrão do Windows 8 é 1366 x 768, e caso o seu monitor não estiver com esta configuração, é possível que algumas informações sejam truncadas. Isto não será um problema quando eu ensinar a usar o Simulator na próxima aula.

Dn205307.A75A97EC907E2DF33B591B9AAAD8E309(pt-br,MSDN.10).png

Figura 27 – Novo layout da página de detalhes do prato

  1. Retorne ao Visual Studio e interrompa a execução.

Resumo

Neste exercício você criou uma aplicação no estilo Modern UI com o template Grid App no Visual Studio, alterou os dados de exemplos com dados reais, modificou as imagens padrão para as customizadas nos Assets, customizou toda a interface de usuário ajustando o XAML fornecido pelo Visual Studio. Complementando, você aprendeu toda a estrutura do projeto, os códigos em C#, XAML e estilos.

Outra funcionalidade demonstrada foi o uso do System.Net.Http.HttpClient para mostrar que você pode trazer dados remotamente do Azure, e como que as classes do WinRT’s Windows.Data.Json podem ser usadas para consumir dados em JSON com C#. A partir de agora, você pode testar esta funcionalidade do Azure. Basta descomentar a linha no App.xaml.cs para chamar o método LoadRemoteDataAsync e comentar a linha LoadLocalDataAsync para dados locais. Antes de executar a aplicação, certifique-se que há conexão com a internet.

Foi um excelente início de projeto, mas ainda há muito o que fazermos para este projeto do Cardápio Eletrônico no estilo Modern UI.

Sobre o Autor

Renato Haddad (rehaddad@msn.comwww.renatohaddad.com ) é MVP, MCT, MCPD e MCTS, palestrante em eventos da Microsoft em diversos países, ministra treinamentos focados em produtividade com o VS.NET 2010/2012, ASP.NET 4, ASP.NET MVC, Entity Framework, Reporting Services, Windows Phone e Windows 8. Visite o blog http://weblogs.asp.net/renatohaddad .

Mostrar: