Criar um aplicativo leitor de blog (C++)

Applies to Windows only

Do começo ao fim, veja como usar C++ e XAML para desenvolver um aplicativo da Windows Store que você pode usar para encontrar e ler blogs de feeds RSS 2.0 ou Atom 1.0.

Este tutorial pressupõe que você já está familiarizado com os conceitos nas Lições 1 a 4 em Criar seu primeiro aplicativo da Windows Store em C++.

Para estudar a versão concluída desse aplicativo, você pode baixá-la no site Galeria de Códigos do MSDN.

Depois de concluir este tutorial, vale a pena ler Desenvolvendo um aplicativo da Windows Store de ponta a ponta em C++ e XAML: Hilo para aprender mais como usar o C++ moderno, o Tempo de Execução do Windows, a programação assíncrona, o XAML e os padrões de desenvolvimento, como MVVM (Model-View-ViewModel), nos aplicativos da Windows Store desenvolvidos em C++.

Se preferir usar outra linguagem de programação, confira:

Objetivos

Este tutorial para ajudá-lo a entender como criar aplicativos da Windows Store com várias páginas e também como e quando usar as extensões de componente do Visual C++ (C++/CX) para simplificar o trabalho de codificação com base no Tempo de Execução do Windows. Esse tutorial também ensina como usar a classe concurrency::task para consumir APIs assíncronas do Tempo de Execução do Windows.

O aplicativo SimpleBlogReader apresenta estes recursos:

  • Suporte a vários feeds de blogs.
  • Suporte ao PLM (Gerenciamento do Tempo de Vida do Processo), além de salvar e recarregar corretamente seu estado, se o sistema for desligado enquanto outra tarefa estava em primeiro plano.
  • Adaptável a vários tamanhos de janelas e orientações de dispositivos (paisagem ou retrato).
  • Usa animações e transições simples para adicionar vida à interface do usuário.
  • Usa estilos para aumentar o apelo visual do aplicativo.

Parte 1: criando o projeto

Para começar, vamos usar o modelo Aplicativo em Branco de aplicativo da Windows Store em C++ para criar um projeto.

Hh465045.wedge(pt-br,WIN.10).gifPara criar o projeto

  1. No Visual Studio, selecione Arquivo > Novo > Projeto; escolha Instalado > Visual C++ > Windows Store; e depois selecione o modelo Aplicativo em Branco (XAML). Para obter instruções mais completas, veja Hello World em C++.

  2. Nomeie o projeto "SimpleBlogReader".

Especificando recursos do aplicativo

Um aplicativo da Windows Store é executado em um contêiner de segurança que tem acesso limitado ao sistema de arquivos, aos recursos de rede e ao hardware. Quando um usuário instala um aplicativo da Windows Store, o Windows verifica os metadados no arquivo Package.appxmanifest para determinar os recursos de aplicativo necessários. Por exemplo, um aplicativo pode ter acesso a dados da Internet, aos documentos da Biblioteca de Documentos do usuário ou à webcam e ao microfone do usuário. Quando o aplicativo é instalado, ele mostra ao usuário os recursos de que ele precisa, e o usuário deve conceder permissão para que ele possa acessar esses recursos. Se o aplicativo não receber permissão para um recurso necessário, esse aplicativo não terá acesso ao recurso no tempo de execução. Nosso leitor do blog precisa ter acesso à Internet. Por isso, é preciso assegurar que esse tenha sido solicitado.

Hh465045.wedge(pt-br,WIN.10).gifPara verificar se a funcionalidade básica de Internet foi solicitada

  1. No Gerenciador de Soluções, abra Package.appxmanifest. O arquivo é aberto no Designer de Manifesto de Aplicativo.

  2. Selecione a guia Recursos.

    Observe que a caixa de seleção Internet (Cliente) já está marcada, com base nas configurações do modelo Em Branco. Se precisássemos de algum outro recurso, poderíamos tê-lo marcado neste designer.

  3. Feche o designer de manifesto.

Geralmente, use o Application Manifest Designer para especificar recursos e o designer grava as especificações no elemento Capabilities do arquivo Package.appxmanifest.xml, para que você não tenha que modificá-lo manualmente. Mas vamos abrir Package.appxmanifest.xml no Editor de Texto XML para examinar como o elemento Capabilities é estruturado.


<Capabilities>    
    <Capability Name="internetClient" />
</Capabilities>


Para saber mais sobre os recursos do aplicativo, veja Designer de Manifesto.

Parte 2: obtendo dados em um aplicativo

Agora podemos escrever o código para obter o feed do blog no aplicativo. O blog "Desenvolvendo para o Windows" expõe o texto completo das postagens nos formatos RSS e Atom. No aplicativo, queremos exibir o título, o autor, a data e o conteúdo de cada uma das postagens mais recentes do blog. Podemos usar as classes do namespace Windows::Web::Syndication para baixar os feeds. (Também é possível usar essas classes para exibir os dados na interface do usuário mas, em vez disso, vamos criar as nossas próprias classes de dados, para que possamos tratar os feeds RSS e Atom da mesma maneira.) Criamos três classes:

  • FeedData contém informações sobre um feed RSS ou Atom.

  • FeedItem contém informações sobre postagens individuais do blog no feed.

  • FeedDataSource contém os métodos para baixar os feeds e inicializar nossas classes de dados.

Definimos essas classes como classes de referência pública para habilitar a ligação de dados para elementos XAML que exibam o Título, Autor, etc. Usamos o atributo Bindable para indicar ao compilador XAML que estamos realizando a vinculação dinâmica a instâncias desses tipos. Em uma classe de referência pública, os membros de dados públicos são expostos como propriedades. Propriedades que não possuem lógica especial não requerem getter e setter especificados pelo usuário — o compilador os fornecerá. Na classe FeedData, observe como IVector é usado para expor um tipo de coleção pública a outras classes e componentes de Tempo de Execução do Windows. Também usamos a classe Platform::Collections::Vectorinternamente como o tipo concreto que implementa IVector. Posteriormente, aprenderemos como consumir esse tipo.

Hh465045.wedge(pt-br,WIN.10).gifPara criar classes de dados personalizados

  1. No Gerenciador de Soluções, no menu de atalho do nó do projeto SimpleBlogReader, escolha Adicionar > Novo Item.

  2. Selecione a opção Arquivo de cabeçalho (.h) e nomeie-o FeedData.h.

  3. Abra FeedData.h e cole o seguinte código nele. Reserve um momento para observar o código e familiarize-se com os construtores C++/CX. Observe que a diretiva #include para "pch.h"—que é onde colocar <string>, <vector> e outros cabeçalhos do sistema. Por padrão, pch.h inclui collection.h, que é necessário para o tipo Platform::Collections::Vector. Para saber mais, veja Classes e estruturas (C++/CX).

    
    
    //feeddata.h
    
    #pragma once
    #include "pch.h"
    
    
    namespace SimpleBlogReader
    {
        // To be bindable, a class must be defined within a namespace
        // and a bindable attribute needs to be applied.
        // A FeedItem represents a single blog post.
        [Windows::UI::Xaml::Data::Bindable]
        public ref class FeedItem sealed
        {
        public:
            FeedItem(void){}
    
            property Platform::String^ Title;
            property Platform::String^ Author;
            property Platform::String^ Content;      
            property Windows::Foundation::DateTime PubDate;      
            property Windows::Foundation::Uri^ Link;
    
        private:
            ~FeedItem(void){}
        };
    
        // A FeedData object represents a feed that contains 
        // one or more FeedItems. 
        [Windows::UI::Xaml::Data::Bindable]
        public ref class FeedData sealed
        {
        public:
            FeedData(void)
            {
                m_items = ref new Platform::Collections::Vector<FeedItem^>();
            }
    
            // The public members must be Windows Runtime types so that
            // the XAML controls can bind to them from a separate .winmd.
            property Platform::String^ Title;            
            property Windows::Foundation::Collections::IVector<FeedItem^>^ Items
            {
                Windows::Foundation::Collections::IVector<FeedItem^>^ get() {return m_items; }
            }
    
            property Platform::String^ Description;
            property Windows::Foundation::DateTime PubDate;
            property Platform::String^ Uri;
    
        private:
            ~FeedData(void){}
    
            Platform::Collections::Vector<FeedItem^>^ m_items;
        };   
    
        // A FeedDataSource represents a collection of FeedData objects
        // and provides the methods to download the source data from which
        // FeedData and FeedItem objects are constructed. This class is 
        // instantiated at startup by this declaration in the 
        // ResourceDictionary in app.xaml: <local:FeedDataSource x:Key="feedDataSource" />
        [Windows::UI::Xaml::Data::Bindable]
        public ref class FeedDataSource sealed
        {
        private:
            Platform::Collections::Vector<FeedData^>^ m_feeds;
            std::map<Platform::String^, concurrency::task_completion_event<FeedData^>> m_feedCompletionEvents;
            FeedData^ GetFeedData(Platform::String^ feedUri, Windows::Web::Syndication::SyndicationFeed^ feed);       
    
        public:
            FeedDataSource();
            property Windows::Foundation::Collections::IObservableVector<FeedData^>^ Feeds
            {
                Windows::Foundation::Collections::IObservableVector<FeedData^>^ get()
                {
                    return this->m_feeds;
                }
            }
            void InitDataSource();
            static Windows::Foundation::IAsyncOperation<FeedData^>^ GetFeedAsync(Platform::String^ title);
            static FeedItem^ GetFeedItem(FeedData^ fd, Platform::String^ uniqueiD);
        };
    }
    
    
  4. Agora crie um novo arquivo .cpp: no menu de atalho para o nó do projeto SimpleBlogReader, escolha Adicionar > Novo Item, e então selecione a opção C++ File (.cpp) e nomeie o arquivo FeedData.cpp. Abra o arquivo e cole esse código nele:

    
    
    #include "pch.h"
    #include "FeedData.h"
    
    
    using namespace std;
    using namespace concurrency;
    using namespace SimpleBlogReader;
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Web::Syndication;
    
    
    
    FeedDataSource::FeedDataSource()
    {
        m_feeds = ref new Vector<FeedData^>();
    }
    
    // Retrieve the data for each atom or rss feed and put it
    // into our custom data structures.
    void FeedDataSource::InitDataSource()
    {
        // Left as an exercise: store the urls separately and let the user configure them.
        // It might be more convenient to use Platform::Strings here, but using wstring 
        // serves to demonstrate how standard C++ types can be used here.
        std::vector<std::wstring> urls; 
        urls.push_back(L"http://windowsteamblog.com/windows/b/developers/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/windowsexperience/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/extremewindows/atom.aspx");
    
        urls.push_back(L"http://windowsteamblog.com/windows/b/business/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/bloggingwindows/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/windowssecurity/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/springboard/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/windowshomeserver/atom.aspx");
        // There is no Atom feed for this blog, so we use the RSS feed.
        urls.push_back(L"http://windowsteamblog.com/windows_live/b/windowslive/rss.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows_live/b/developer/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/ie/b/ie/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows_phone/b/wpdev/atom.aspx");
    
        // If we are resuming, we need to create a map of completion events so that
        // we don't attempt to restore page's state before it's backing data has been loaded.
        // First we create all the  events in an "unset" state, mapped to the urls as keys.
        // we'll set the event after we asynchronously load the feed.
        for( wstring url : urls)
        {
            auto uri = ref new String(url.c_str());
            task_completion_event<FeedData^> e;
            m_feedCompletionEvents.insert(make_pair(uri, e));
        }
    
        SyndicationClient^ client = ref new SyndicationClient();   
    
        // Range-based for loop. Never write a regular for loop again!
        for(wstring url : urls)
        {
            // Create the async operation. feedOp is an 
            // IAsyncOperationWithProgress<SyndicationFeed^, RetrievalProgress>^
    
            auto uri = ref new String(url.c_str());
            auto feedUri = ref new Uri(uri);
            auto feedOp = client->RetrieveFeedAsync(feedUri);
    
            // Create the task object and pass it the async operation.
            // SyndicationFeed^ is the type of the return value
            // that the feedOp operation will eventually produce.       
    
            // Then, initialize a FeedData object with the feed info. Each
            // operation is independent and does not have to happen on the
            // UI thread. Therefore, we specify use_arbitrary.
            create_task(feedOp)
    
            .then([this, uri]  (SyndicationFeed^ feed) -> FeedData^
            {
                return GetFeedData(uri, feed);
            }, concurrency::task_continuation_context::use_arbitrary())
    
    
            // Append the initialized FeedData object to the list
            // that is the data source for the items collection.
            // This has to happen on the UI thread. By default, a .then
            // continuation runs in the same apartment thread that it was called on.
            // Because the actions will be synchronized for us, we can append 
            // safely to the Vector without taking an explicit lock.
            .then([this] (FeedData^ fd)
            {
                m_feeds->Append(fd);
                m_feedCompletionEvents[fd->Uri].set(fd);
    
                // Write to VS output window in debug mode only. Requires <windows.h>.
                OutputDebugString(fd->Title->Data());
                OutputDebugString(L"\r\n");
            })
    
            // The last continuation serves as an error handler. The
            // call to get() will surface any exceptions that were raised
            // at any point in the task chain.
            .then( [] (task<void> t)
            {
                try
                {
                    t.get();
                }
                // SyndicationClient throws Platform::InvalidArgumentException 
                // if a URL contains illegal characters.
                // We catch this exception for demonstration purposes only.
                // In the current design of this app, an illegal
                // character can only be introduced by a coding error
                // and should not be caught. If we modify the app to allow
                // the user to manually add a new url, then we need to catch
                // the exception.
                catch(Platform::InvalidArgumentException^ e)
                {
                    // For example purposes we just output error to console.
                    // In a real world app that allowed the user to enter
                    // a url manually, you could prompt them to try again.
                    OutputDebugString(e->Message->Data());
                }
            }); //end task chain
        };
    }
    
    
    FeedData^ FeedDataSource::GetFeedData(String^ feedUri, SyndicationFeed^ feed)
    {
    
        FeedData^ feedData = ref new FeedData();
    
        // Knowing this makes it easier to map completion_events 
        // when we resume from termination.
        feedData->Uri = feedUri;
    
        // Get the title of the feed (not the individual posts).
        feedData->Title = feed->Title->Text; 
    
        if (feed->Subtitle->Text != nullptr)
        {
            feedData->Description = feed->Subtitle->Text;
        }	 
        // Use the date of the latest post as the last updated date.
        feedData->PubDate = feed->Items->GetAt(0)->PublishedDate;	
    
        // Construct a FeedItem object for each post in the feed
        // using a range-based for loop. Preferable to a 
        // C-style for loop, or std::for_each.
        for (auto  item : feed->Items)
        {
            auto feedItem = ref new FeedItem();
            feedItem->Title = item->Title->Text; 
            feedItem->PubDate = item->PublishedDate;		
    
            //We only get first author in case of multiple entries.
            feedItem->Author = item->Authors->GetAt(0)->Name; 
    
            if (feed->SourceFormat == SyndicationFormat::Atom10)
            {
                feedItem->Content = item->Content->Text;
                feedItem->Link = ref new Uri(item->Id);
            }
    
            else if (feed->SourceFormat == SyndicationFormat::Rss20)
            {
                feedItem->Content = item->Summary->Text;
                feedItem->Link = item->Links->GetAt(0)->Uri;
            }
    
            feedData->Items->Append(feedItem);
        };
    
        return feedData;
    
    } //end GetFeedData
    
    
    // We use this method to get the proper FeedData object when resuming
    // from shutdown. We need to wait for this data to be populated before
    // we attempt to restore page state. Note the use of task_completion_event
    // which doesn't block the UI thread.
    IAsyncOperation<FeedData^>^ FeedDataSource::GetFeedAsync(String^ uri)
    {
        return create_async([uri]()
        {
            auto feedDataSource = safe_cast<FeedDataSource^>( 
                App::Current->Resources->Lookup("feedDataSource"));
    
            // Does not block the UI thread.
            auto f = feedDataSource->m_feedCompletionEvents[uri];
    
            // In the callers we continue from this task after the event is 
            // set in InitDataSource and we know we have a FeedData^.
            task<FeedData^> t = create_task(f);
            return t;
        });
    }
    
    // We stored the stringID when the app was suspended
    // because storing the FeedItem itself would have required
    // more custom serialization code. Here is where we retrieve
    // the FeedItem based on its string ID.
    FeedItem^ FeedDataSource::GetFeedItem(FeedData^ feed, String^ uniqueId)
    {
        auto itEnd = end(feed->Items);
        auto it = std::find_if(begin(feed->Items), itEnd, 
            [uniqueId] (FeedItem^ fi)
        {
            return fi->Title == uniqueId;
        });
    
        if (it != itEnd)
            return safe_cast<FeedItem^>(*it);
    
        return nullptr;
    }
    
    
    

    A classe Windows.Web.Syndication.SyndicationClient recupera e analisa feeds RSS e Atom. Como essa operação envolve E/S de rede, o método é executado de forma assíncrona. O modelo de programação assíncrona é encontrado em todas as bibliotecas de classes de Tempo de Execução do Windows. Uma chamada de método assíncrono retorna o controle imediatamente para o thread da Interface do Usuário, de modo que esta última permaneça responsiva enquanto a operação é executada em um thread em segundo plano.

    O Tempo de Execução do Windows fornece uma forma de invocar operações assíncronas e obter seus resultados quando estiverem concluídos. Você pode programar diretamente com relação a essa API, mas a abordagem preferida é usar o task class que está definida em ppltasks.h, que está incluído em pch.h. A classe task invoca essas mesmas APIs assíncronas de Tempo de Execução do Windows, mas usando-a, você pode escrever códigos mais concisos, operações assíncronas em cadeia mais facilmente e manipular diante de excepções que possa surgir. A classe task é usada em dois lugares no arquivo que acabamos de criar. Quando você usa a classe task, as etapas básicas são sempre as mesmas:

    1. Crie uma operação assíncrona chamando um método de Tempo de Execução do Windows*Async como Windows::Web::Syndication::ISyndicationClient::RetrieveFeedAsync.

    2. Crie um objeto task chamando simultaneamente::create_task e usando a operação como um parâmetro de entrada.

    3. Defina uma tarefa que executará quando a tarefa original concluir chamando task::then e especificando um lambda que pega o valor de retorno da tarefa original como entrada.

    4. Opcionalmente, chame then novamente, uma ou mais vezes. Essas cláusulas podem aceitar o valor de retorno da cláusula anterior.

    5. Forneça uma cláusula then final que manipula quaisquer exceções que tenham sido lançadas em qualquer lugar na cadeia de operações. Essa etapa é opcional, mas altamente recomendada.

Parte 3: criando uma classe date-formatting personalizada

Agora é uma boa ideia adicionar mais uma classe personalizada, uma implementação de IValueConverter, que é uma interface para converter tipos arbitrários em formas arbitrárias. Nossa classe é um Date Converter que retorna o dia, o mês e o ano como cadeias de caracteres individuais, quando um argumento DateTime é fornecido. Usaremos essa classe no estilo visual que será definido para itens de grade e itens de modo de exibição de lista, mais adiante.

Hh465045.wedge(pt-br,WIN.10).gifPara criar uma classe que implementa IValueConverter

  • No projeto SimpleBlogReader, adicione um novo cabeçalho nomeado DateConverter.h e cole esta implementação nele:

    
    
    //DateConverter.h
    
    #pragma once
    #include <string> //for wcscmp
    
    namespace SimpleBlogReader
    {
    
    public ref class DateConverter sealed : public Windows::UI::Xaml::Data::IValueConverter  
    {
        public:
        virtual Platform::Object^ Convert(Platform::Object^ value,
                                          Windows::UI::Xaml::Interop::TypeName targetType,
                                          Platform::Object^ parameter,
                                          Platform::String^ language)
        {		
            if(value == nullptr)
            {
                throw ref new Platform::InvalidArgumentException();
            }
            auto dt = safe_cast<Windows::Foundation::DateTime>(value);
            auto param = safe_cast<Platform::String^>(parameter);
            Platform::String^ result;
            if(param == nullptr)
            {
                auto dtf =
                Windows::Globalization::DateTimeFormatting::DateTimeFormatter::ShortDate::get();
                result = dtf->Format(dt);
            }
            else if(wcscmp(param->Data(), L"month") == 0)
            {
                auto month = 
                    ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{month.abbreviated(3)}");
                result = month->Format(dt);
            }
            else if(wcscmp(param->Data(), L"day") == 0)
            {
                auto month = 
                   ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{day.integer(2)}");
                result = month->Format(dt);
            }
            else if(wcscmp(param->Data(), L"year") == 0)
            {
                auto month = 
                    ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{year.full}");
    				        result = month->Format(dt);
            }
            else
            {
                // We don't handle other format types currently.
                throw ref new Platform::InvalidArgumentException();
            }
    
            return result; 
         }
    
        virtual Platform::Object^ ConvertBack(Platform::Object^ value,
                                              Windows::UI::Xaml::Interop::TypeName targetType,
                                              Platform::Object^ parameter,
                                              Platform::String^ language)
        {   
            // Not needed in SimpleBlogReader. Left as an exercise.
            throw ref new Platform::NotImplementedException();
        }
    };
    }
    
    

Hh465045.wedge(pt-br,WIN.10).gifPara fazer com que o aplicativo reconheça classes personalizadas

  1. Para fazer com que o aplicativo (especificamente, seus controles XAML) reconheça nossas classes personalizadas, primeiro precisamos incluir seus arquivos de cabeçalho para que sejam compilados. Isso será feito com a adição do código ao App.xaml.h:
    
    
    #include "FeedData.h"
    #include "DateConverter.h"
    
    
  2. Queremos que as classes FeedDataSource e DateConverter fiquem ativas enquanto o aplicativo estiver ativo, então iremos armazená-las como recursos do aplicativo. Isso faz com que elas sejam instanciadas quando o aplicativo é iniciado. Faremos referência das instâncias em todo o aplicativo, em XAML e no code-behind usando o valor x:Key definido no código a seguir.

    Para implementar essas referências, no App.xaml, logo antes da tag de fechamento do elemento Application, digite <Application.Resources> (ou use o Preenchimento de Declaração dinâmico para adicionar esse elemento). Em seguida, adicione os recursos FeedDataSource e DateConverter para que o nó <Application> fique semelhante a:

    
     <Application
        x:Class="SimpleBlogReader.App"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:SimpleBlogReader">
        <Application.Resources>
            <local:FeedDataSource x:Key="feedDataSource" />
            <local:DateConverter x:Key="dateConverter" />
        </Application.Resources>
    </Application>
    
    
    
  3. Agora é um bom momento para verificar se o aplicativo é compilado sem erros, mesmo se ele não estiver fazendo nada neste momento. Para compilar a instância de depuração do aplicativo e exibi-la, pressione F5. Para voltar para o desenvolvimento, retorne para o Visual Studio e pressione Shift+F5.

Parte 4: adicionando páginas e navegação

Agora estamos prontos para criar a interface do usuário. Se a intenção for dar suporte a vários blogs, precisaremos acrescentar páginas ao aplicativo e trabalhar a navegação entre elas. Felizmente, o Visual Studio inclui vários modelos de página que implementam a maioria das funcionalidades de navegação e orientação de página necessárias.

Primeiro, queremos uma página que liste os Blogs da Equipe do Windows. Para isso, podemos usar um modelo Página de Itens. Quando um leitor escolhe um blog dessa página, carregamos a lista de postagens do blog em outra página, que terá como base o modelo Dividir Página. Também usaremos o modelo Página Básica para adicionar uma página de detalhes, para que o usuário possa ler postagens de blog individuais sem que o modo de exibição de lista ocupe espaço. Esses modelos já dão suporte à navegação de que precisamos, por isso, não há necessidade de codificação e podemos nos concentrar nas tarefas principais – escrever lógica personalizada para os métodos LoadState e SaveState que foram desfragmentados em cada classe code-behind e adicionar uma barra de aplicativos contendo um botão para habilitar a navegação progressiva da página de divisão para a página de detalhes.

Hh465045.wedge(pt-br,WIN.10).gifPara adicionar páginas ao aplicativo

  1. No Gerenciador de Soluções, abra o menu de atalho de MainPage.xaml e selecione Remover. Não usaremos essa página ou respectivos arquivos code-behind em nosso projeto. Você pode excluí-la permanentemente ou apenas removê-la.

  2. No App.xaml.cpp, altere a diretiva #include de MainPage.xaml.h para #include ItemsPage.xaml.h.

  3. Ainda no App.xaml.cpp, pressione Ctrl+H para encontrar as instâncias de "MainPage" e substitua-as por "ItemsPage." Observe que as instâncias de "ItemsPage" têm um sublinhado vermelho ondulado e isso desaparecerá quando você adicionar a nova página.

  4. Abra o menu de atalho do projeto SimpleBlogReader e selecione Adicionar > Novo Item.

  5. Na caixa de diálogo Adicionar Novo Item, no painel Instalado, selecione Visual C++ > Windows Store.

    No painel central, selecione Página de Itens, aceite o nome padrão e selecione o botão Adicionar.

    Na caixa de diálogo que pergunta se você deseja adicionar os arquivos necessários automaticamente, selecione Sim. Esses arquivos contêm código para dar suporte à navegação, serialização e desserialização de tipos simples quando o aplicativo é encerrado e retomado. Eles são adicionados à pasta \Common\.

  6. Repita as etapas anteriores, mas selecione o modelo de Dividir Página.

  7. Repita as etapas novamente, mas selecione o modelo de Página Básica e nomeie a página como DetailPage.

A página Itens mostrará a lista de Blogs da Equipe do Windows. A página Dividida mostrará as postagens para cada blog no lado esquerdo e o conteúdo da postagem selecionada no lado direito. A página Detalhes não mostrará nada além do conteúdo de uma postagem selecionada, um botão Voltar e o título da página. Nessa página, em vez de carregar o conteúdo da postagem para o WebView a partir de uma cadeia de caracteres HTML como fazemos na página Dividida, navegamos até a URL da postagem e mostramos a página da Web propriamente dita. Depois de implementar isso, as páginas do aplicativo terão uma aparência semelhante a esta:

Modelo de navegação de três páginas

Quando examinamos o XAML e o code-behind das páginas adicionadas, é evidente que esses modelos de página fazem boa parte do trabalho para nós. Na verdade, como é fácil ficar confuso no começo, eles ajudam a entender que cada modelo de página tem três seções principais:

Recursos

Estilos e modelos de dados da página são definidos na seção Recursos. Falaremos mais sobre isso na seção Criando uma aparência consistente com estilos.

Gerenciador de Estado Visual

Animações e transições que adaptam o aplicativo a diferentes layouts e orientações são definidas no VSM (Gerenciador de Estado Visual). Falaremos mais sobre isso na seção Adaptando-se a diferentes layouts.

Conteúdo do Aplicativo

Os controles e o conteúdo que formam a Interface do Usuário do aplicativo são definidos no painel de layout raiz.

 

Navegando entre as páginas

Uma das atividades principais em um aplicativo da Windows Store é navegar entre páginas. Um usuário pode ser capaz de escolher um botão avançar ou voltar para percorrer as páginas ou escolher um item que abre outra página (por exemplo, uma página que mostra os detalhes sobre o item). Este é o design de navegação para SimpleBlogReader:

  • Quando o aplicativo é iniciado, a página Itens mostra uma grade de feeds de blog (objetos DataFeed). Apenas os títulos de blog são exibidos, juntamente com as descrições (se existirem) e as datas das últimas mensagens. Quando o usuário seleciona um feed, o aplicativo navega para a página Dividida.

  • No modo paisagem, a página Dividida mostra a lista de postagens (objetos FeedItem) à esquerda e uma visualização da postagem selecionada à direita. No modo retrato, a página Dividida mostra a lista ou a visualização, mas não as duas ao mesmo tempo. Quando o usuário seleciona um outro item na lista, a visualização é alterada.

  • A página Dividida tem uma barra de aplicativos que o usuário pode invocar deslizando da parte superior da tela ou clicando com o botão direito do mouse. Quando o usuário seleciona o botão na barra de aplicativos, o aplicativo navega para a página Detalhes que mostra a postagem de blog como uma página da Web completa.

  • Na página Detalhes, o usuário pode selecionar o botão Voltar para retornar à página de divisão. A página de divisão também tem um botão Voltar para retornar à página Itens.

Para complicar as coisas, sempre que carregamos uma página, devemos considerar se a navegação é invocada por uma ação do usuário (para frente ou para trás) ou pelo sistema que está reiniciando o aplicativo após o respectivo encerramento em algum momento anterior.

A classe SuspensionManager (no SuspensionManager.h e no SuspensionManager.cpp da pasta \Common\) fornece a maior parte do código necessário para salvar o estado da página quando o aplicativo é suspenso ou encerrado de modo que ele possa ser restaurado posteriormente. No entanto, cabe a nós implementar a lógica específica que é necessária para carregar ou recarregar as páginas.

A estrutura de interface do usuário XAML oferece um modelo de navegação interno que usa Frames e Pages, e funciona de maneira semelhante à navegação em um navegador da Web. O controle Frame hospeda Pages e mantém um histórico de navegação que você pode usar para avançar e retroceder pelas páginas visitadas. Além disso, você pode transmitir dados entre páginas enquanto navega.

O histórico de navegação começa quando o aplicativo é iniciado. A infraestrutura que dá suporte à navegação está na classe App. Nos modelos de projeto Visual Studio, um Frame chamado rootFrame é definido como o conteúdo da janela do aplicativo. No App.xaml.cpp, vamos observar o código fornecido pelo modelo. Observe que depois que o rootFrame é definido, o aplicativo verifica seu estado atual porque ele pode inicializar de um estado encerrado ou sair do modo de suspensão e já ter seu conteúdo na memória. Se estiver iniciando de um estado encerrado, ele terá que carregar o estado salvo quando ele foi encerrado. Depois de lidar com esses diversos casos, o aplicativo navega para sua primeira janela.



// Default implementation. Don't paste this into SimpleBlogReader.
 
/// <summary>
/// Invoked when the application is launched normally by the end user.  Other entry points
/// will be used when the application is launched to open a specific file, to display
/// search results, and so forth.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
void App::OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs^ e)
{

#if _DEBUG
    if (IsDebuggerPresent())
    {
        DebugSettings->EnableFrameRateCounter = true;
    }
#endif

    auto rootFrame = dynamic_cast<Frame^>(Window::Current->Content);

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active
    if (rootFrame == nullptr)
    {
        // Create a Frame to act as the navigation context and associate it with
        // a SuspensionManager key
        rootFrame = ref new Frame();

        if (e->PreviousExecutionState == ApplicationExecutionState::Terminated)
        {
            // TODO: Restore the saved session state only when appropriate, scheduling the
            // final launch steps after the restore is complete

        }

        if (rootFrame->Content == nullptr)
        {
            // When the navigation stack isn't restored navigate to the first page,
            // configuring the new page by passing required information as a navigation
            // parameter
            if (!rootFrame->Navigate(TypeName(ItemsPage::typeid), e->Arguments))
            {
                throw ref new FailureException("Failed to create initial page");
            }
        }
        // Place the frame in the current Window
        Window::Current->Content = rootFrame;
        // Ensure the current window is active
        Window::Current->Activate();
    }
    else
    {
        if (rootFrame->Content == nullptr)
        {
            // When the navigation stack isn't restored navigate to the first page,
            // configuring the new page by passing required information as a navigation
            // parameter
            if (!rootFrame->Navigate(TypeName(ItemsPage::typeid), e->Arguments))
            {
                throw ref new FailureException("Failed to create initial page");
            }
        }
        // Ensure the current window is active
        Window::Current->Activate();
    }
}


Para habilitar a navegação entre páginas, usamos os métodos Navigate, GoForward e GoBack do controle Frame. Usando o método Navigate(TypeName, Object), podemos navegar para uma nova página e transmitir os dados para ela ao mesmo tempo. O primeiro parâmetro é o TypeName da página para a qual estamos navegando. Usamos o operador typeid estático para obter o TypeName de um tipo. Nesse caso, desejamos navegar para a página Itens como a primeira página que os usuários encontram quando iniciam ou retomam o aplicativo.

Cada página tem como membro particular um objeto NavigationHelper, que é gerado automaticamente e fica na pasta \Common\ em NavigationHelper.h e NavigationHelper.cpp. Quando a página é navegada, o NavigationHelper realiza o gerenciamento básico da infraestrutura de navegação. Uma das coisas que ele faz é chamar LoadState e SaveState nos momentos apropriados. Como desenvolvedor de aplicativo, sua tarefa é adicionar lógica aos métodos LoadState e SaveState da página para ter sempre os dados corretos para vinculação e recuperá-los quando o aplicativo é encerrado.

O primeiro parâmetro em LoadState e SaveState é a página que enviou o comando de navegação. O segundo parâmetro é um LoadStateEventArgs que inclui, dentre outras coisas, uma propriedade PageState com os dados que passamos quando chamamos Navigate. SuspensionManager tentará serializar qualquer objeto passado por meio desse parâmetro, mas sem a nossa ajuda, poderá ter sucesso apenas se o tipo for uma Cadeia de Caracteres, um Guid ou um tipo primitivo. Portanto, não passaremos um objeto FeedData inteiro, mas poderemos passar uma cadeia de caracteres e usá-la como uma chave para pesquisar o objeto FeedData ou FeedItem específico. Esta é a finalidade dos métodos GetFeedAsync e GetFeedItem na classe FeedDataSource. Analisaremos esses métodos em mais detalhes posteriormente.

Hh465045.wedge(pt-br,WIN.10).gifPara navegar da classe App até a página Itens

  1. Em App.xaml.cpp, o método App::OnLaunched verifica se rootFrame já existe e, caso não exista, o código criará um. Se estamos criando um novo rootFrame, vamos ter também que baixar e inicializar nossos itens de feed. Esse código é acessado quando o aplicativo é iniciado após ser encerrado pelo sistema ou fechado pelo usuário. Se não ocorreu nenhum desses eventos, os itens de feed continuarão na memória.

    Em App.xaml.cpp, no método OnLaunched, adicione esse código após a instrução rootFrame = ref new Frame();:

    
    
    
        FeedDataSource^ feedDataSource = safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
        if (feedDataSource->Feeds->Size == 0)
        {
            feedDataSource->InitDataSource();
        }
    
    
    

    Quando o método Navigate é invocado em App::OnLaunched, ele acaba fazendo com que o manipulador de eventos ItemsPage::LoadState seja chamado. Conforme discutido anteriormente, não podemos passar o objeto FeedDataSource ou a propriedade Feeds porque nós não queremos ter que serializá-los. Em vez disso, armazenamos uma referência ao FeedDataSource como um recurso no App.xaml e apenas o acessamos aqui. Ainda precisamos usar a propriedade Feeds para iniciar o DefaultViewModel para ItemsPage. Aqui, o Vetor de Feeds é associado a uma chave que se chama "Items" e inserido no membro DefaultViewModel da ItemsPage. DefaultViewModel é um Windows::Foundation::Collections::IObservableMap. Cada página tem seu próprio DefaultViewModel. Depois que você inicializar o DefaultViewModel com alguns dados, a propriedade ItemsViewSource::View apontará para os seus dados. Se os elementos na coleção forem associáveis, os itens aparecerão na interface do usuário.

  2. Em ItemsPage.xaml.cpp, substitua o método LoadState por esse código:

    
    
    void ItemsPage::LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e)
    {
        (void)sender;	// Unused parameter
        (void)e;	// Unused parameter
    
        // This is the first page to load on startup. The feedDataSource was constructed when the app loaded
        // in response to this declaration in app.xaml: <local:FeedDataSource x:Key="feedDataSource" />
        // and was initialized aynchronously in the OnLaunched event handler in app.xaml.cpp. 
        // Initialization might still be happening, but that's ok. 
        auto feedDataSource = safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
    
        // In ItemsPage.xaml (and every other page), the DefaultViewModel is set as DataContext:
        // DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
        // Because ItemsPage displays a collection of feeds, we set the Items element
        // to the FeedDataSource::Feeds collection. By comparison, the SplitPage sets this element to 
        // the Items collection of one FeedData object.
        this->DefaultViewModel->Insert("Items", feedDataSource->Feeds);
    
    }
    
    

Agora, pressione F5 para executar o aplicativo. Observe que, sem nenhuma alteração no código do modelo, alguns dos dados que transmitimos a página Itens já estão aparecendo nos quadrados da grade. Ele tem uma aparência semelhante a esta (os itens podem estar organizados de maneira diferente, dependendo da resolução da tela):

Página de Itens

A única coisa que resta a fazer na página Itens é dizer o que deve ser feito quando o usuário escolhe um de seus itens.

Hh465045.wedge(pt-br,WIN.10).gifPara navegar da página Itens até a página Dividida

  1. Quando o usuário escolhe um blog do conjunto ItemsPage, navegamos da página Itens para a página de divisão. Para habilitar essa navegação, queremos que os itens do GridView se comportem como botões e não como itens selecionados. Para fazer com que os itens do GridView reajam como botões, precisamos definir as propriedades SelectionMode e IsItemClickEnabled, como ilustrado no bloco de códigos a seguir. Em seguida, adicionamos um manipulador ao evento ItemClicked do GridView. Em ItemsPage.xaml, localize a marca de abertura do elemento GridView, que está nomeado como itemGridView e tem esta aparência:

    
    
     <GridView
                x:Name="itemGridView"
                AutomationProperties.AutomationId="ItemsGridView"
                AutomationProperties.Name="Items"
                TabIndex="1"
                Grid.RowSpan="2"
                Padding="116,136,116,46"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                SelectionMode="None"
                IsItemClickEnabled="true"
                IsSwipeEnabled="false">
    
    
    

    Agora, posicione o curso antes do colchete de fechamento e pressione Enter para criar uma nova linha após IsSwipeEnabled="false". Digite Margin="0,-10,0,10" e, em uma nova linha, digite ItemClick="itemGridView_ItemClick" . Observe que, ao digitar ItemClick=, o preenchimento automático sugere itemGridView_ItemClick — se aceitar a sugestão do preenchimento automático, isso desfragmentará o code-behind para você.

  2. Em ItemsPage.xaml.cpp, adicione esta diretiva include para que SplitPage seja reconhecida:

    
    #include "SplitPage.xaml.h"
    
    
    
  3. Se usar o nome padrão sugerido para desfragmentar o code-behind, como descrito na etapa 2, o protótipo do manipulador de eventos itemGridView_ItemClick será exibido em ItemsPage.xaml.h e um stub de implementação aparecerá em ItemsPage.xaml.cpp. Basta colar este código no stub de implementação:

    
    
    
        // We must manually cast from Object^ to FeedData^.
        auto feedData = safe_cast<FeedData^>(e->ClickedItem);
    
        // Store the current URI so that we can look up the
        // correct feedData object on resume.
        App::Current->Resources->Insert("CurrentFeed", feedData);
    
        // Navigate to SplitPage and pass the title of the selected feed.
        // SplitPage will receive this in its LoadState method in the navigationParamter.
        this->Frame->Navigate(TypeName(SplitPage::typeid), feedData->Title);
    
    
    
  4. Quando a página Itens navega para a página Dividida, ele faz com que o método SplitPage::LoadState seja chamado. Como na ItemsPage, LoadState tem que determinar como ela foi navegada e qual era o estado anterior do aplicativo. Os comentários do código no próximo exemplo fornecem mais detalhes; os quais você provavelmente reconhecerá olhando para App::OnLaunched e ItemsPage::LoadState.

    Agora, abra SplitPage.xaml.cpp e use este código para substituir o método LoadState inteiro:

    
    void SplitPage::LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e)
    {
        // If we are navigating forward from ItemsPage, there is no existing page state.
        if (e->PageState == nullptr)
        {
            // Current feed was set in the click event in ItemsPage. We don't pass it in
            // through navigationParameter because on suspension, the default serialization 
            // mechanism will try to store that value but fail because it can only handle 
            // primitives, strings, and Guids.
            auto fd = safe_cast<FeedData^>(App::Current->Resources->Lookup("CurrentFeed"));
    
            // Insert into the ViewModel for this page to initialize itemsViewSource->View
            this->DefaultViewModel->Insert("Feed", fd);
            this->DefaultViewModel->Insert("Items", fd->Items);
    
            // When this is a new page, select the first item automatically unless logical page
            // navigation is being used (see the logical page navigation #region below).
            if (!UsingLogicalPageNavigation() && itemsViewSource->View != nullptr)
            {
                this->itemsViewSource->View->MoveCurrentToFirst();
            }
            else
            {
                this->itemsViewSource->View->MoveCurrentToPosition(-1);
            }
        }
    
        // e>PageState != null means either (1) we are returning from DetailPage
        // or (2) we are resuming from termination. If (1), then we still have our
        // state and don't have to do anything. If (2), then we have to restore the page.
        else if (!this->DefaultViewModel->HasKey("Feed"))
        {
            // All we stored is the Uri string for the feed, not the object. 
            auto uri = safe_cast<String^>(e->PageState->Lookup("Uri"));
    
            // FeedDataSource::InitDataSource has already been called. 
            // It's an asynchronous operation, so our FeedData might not
            // be available yet. GetFeedAsync uses a task_completion_event to  
            // wait (on its own thread) until the specified FeedData is available.
            // The next three methods follow the basic async pattern in C++:
            // 1. Call the async method.
            auto feedDataOp = FeedDataSource::GetFeedAsync(uri);
    
            // 2. Create a task from it.
            auto feedDataTask = create_task(feedDataOp);
    
            // 3. Define the work to be performed after the task completes.
            feedDataTask.then([this, e](FeedData^ feedData)
            {
                // Now we have the feedData, so it's safe to get the FeedItem
                // synchronously. Inserting into DefaultViewModel
                // initializes the itemsViewSource-View object.
                this->DefaultViewModel->Insert("Feed", feedData);
                this->DefaultViewModel->Insert("Items", feedData->Items);
    
                // DetailPage has to get the new Uri from this value.
                App::Current->Resources->Insert("CurrentFeed", feedData);
    
                // Now that we have a FeedData^, we can call GetFeedItem safely and
                // pass in the title that we stored before the app was terminated.
                auto itemTitle = safe_cast<String^>(e->PageState->Lookup("SelectedItem"));
                auto selectedItem = FeedDataSource::GetFeedItem(feedData, itemTitle);
    
                if (selectedItem != nullptr)
                {
                    this->itemsViewSource->View->MoveCurrentTo(selectedItem);
                }
            });
        }
    }
    
    
    

    Quando retomamos o aplicativo do encerramento, não temos um objeto FeedItem, temos apenas uma cadeia de caracteres. Assim, temos que usar a cadeia de caracteres para procurar o FeedItem, mas não podemos fazê-lo até o download do FeedItem ser concluído por FeedDataSource. Porém, não queremos esperar a conclusão do download de todos os feeds; queremos aguardar somente até que esteja pronto o feed que precisamos. Toda essa sincronização ocorre no método LoadState e no método FeedDataSource::GetFeedAsync. Depois que temos o objeto FeedData, podemos chamar o método GetFeedItem de forma sincronizada para obter o FeedItem para a postagem selecionada, para que possamos popular o painel de visualização.

    Observação  Inserimos a propriedade Items separadamente no DefaultViewModel para a página Dividida a fim de tornar todos os itens acessíveis à vinculação de dados XAML.

    Nenhum trabalho extra é necessário para navegar de volta à página de Itens. O modelo inclui códigos para tratar o evento BackButton.Click e chamar o método Frame.GoBack.

  5. Ao pressionar F5 para executar o aplicativo nesse ponto e clicar em um item dos dados de feed, você notará que o texto do blog, no painel de visualização na página de divisão, mostrará HTML bruto. Para corrigir isso, precisamos mudar o layout usado para o título e o conteúdo da postagem de blog selecionada. Se o aplicativo estiver em execução, feche-o retornando ao Visual Studio e pressionando Shift+F5.

Hh465045.wedge(pt-br,WIN.10).gifPara implementar SplitPage::SaveState

  • Em ItemsPage, não foi preciso salvar o estado da página porque sempre mostramos todos os itens. No entanto, na SplitPage, precisamos salvar o item atualmente selecionado, para que o aplicativo possa começar exatamente a partir desse estado quando for encerrado e reiniciado. Como mencionado anteriormente, devido ao fato de estamos contando com o SuspensionManager para fazer o trabalho de serialização para nós, só podemos salvar cadeias de caracteres e números, e não objetos FeedItem. Por isso, para o item selecionado atualmente, salvamos o título do objeto FeedItem e o URI do objeto FeedData, de modo que possamos encontrar esse item novamente após o encerramento e a reinicialização.

    Em SplitPage.xaml.cpp, encontre o método SaveState e use este código para substituí-lo:

    
    void SplitPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
        if (itemsViewSource->View != nullptr)
        {
            auto selectedItem = itemsViewSource->View->CurrentItem;
            if (selectedItem != nullptr)
            {
                auto feedItem = safe_cast<FeedItem^>(selectedItem);
                auto itemTitle = feedItem->Title;
                e->PageState->Insert("SelectedItem", itemTitle);
            }
    
            // Save the feed title also.
            auto feedData = safe_cast<FeedData^>(this->DefaultViewModel->Lookup("Feed"));
            e->PageState->Insert("Uri", feedData->Uri);
        }
    }
    
    

Precisamos fazer mais algumas mudanças para terminar de adicionar funcionalidade às nossas páginas. Após a adição desse código, poderemos seguir adiante e definir um estilo e uma animação.

Hh465045.wedge(pt-br,WIN.10).gifPara alterar as vinculações e o layout na SplitPage e na ItemsPage

  1. Primeiro, em SplitPage.xaml, altere a Grid chamada de titlePanel para abranger duas colunas:

    
    <!-- Back button and page title -->
    <Grid x:Name="titlePanel" Grid.ColumnSpan="2">
    
    
  2. Em seguida, como usamos uma chave com o nome "Feed" quando adicionamos nossos dados ao DefaultViewModel, é preciso alterar a associação no título da página para associar a propriedade Feed. Em SplitPage.xaml, altere a associação de texto do TextBlock, cujo nome é pageTitle, para associar a Feed.Title desta forma:

    
    <TextBlock x:Name="pageTitle" Text="{Binding Feed.Title}" 
        Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1" 
        IsHitTestVisible="false" TextWrapping="WrapWholeWords" 
        VerticalAlignment="Bottom" Margin="0,0,30,40"/>
    
    
  3. Em ItemsPage.xaml, o título da página está vinculado a um recurso estático que tem a chave AppName. Altere o texto nesse recurso para Windows Team Blogs. Faça o seguinte:

    
    <x:String x:Key="AppName">Windows Team Blogs</x:String>
    
    

Hh465045.wedge(pt-br,WIN.10).gifPara adicionar um controle do WebView a SplitPage.xaml

  1. Em SplitPage.xaml, também queremos mudar o layout usado para o título e o conteúdo da postagem de blog selecionada. Para fazê-lo, use o layout a seguir para substituir o ScrollViewer nomeado itemDetail. Observe que uma boa parte do XAML se assemelha ao trabalho anterior em MainPage.xaml. Explicaremos mais adiante neste artigo a finalidade do elemento Rectangle.

    Em SplitPage.xaml, recolha o elemento ScrollViewer existente, exclua-o e cole nesta marcação:

    
            <!-- Details for selected item -->
            <ScrollViewer
                x:Name="itemDetail"
                AutomationProperties.AutomationId="ItemDetailScrollViewer"
                Grid.Column="1"
                Grid.RowSpan="2"
                Padding="70,0,120,0"
                DataContext="{Binding SelectedItem, ElementName=itemListView}"
                HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
                ScrollViewer.HorizontalScrollMode="Disabled" ScrollViewer.VerticalScrollMode="Enabled"
                ScrollViewer.ZoomMode="Disabled">
    
                <Grid x:Name="itemDetailGrid" Margin="0,60,0,50">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
    
                    <Image Grid.Row="1" Margin="0,0,20,0" Width="180" Height="180" Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
                    <StackPanel x:Name="itemDetailTitlePanel" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2">
                        <TextBlock x:Name="itemTitle" Margin="0,-10,0,0" Text="{Binding Title}" Style="{StaticResource SubheaderTextBlockStyle}"/>
                        <TextBlock x:Name="itemSubtitle" Margin="0,0,0,20" Text="{Binding Subtitle}" Style="{StaticResource SubtitleTextBlockStyle}"/>
                    </StackPanel>
                 <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" Grid.Row="2" Grid.Column="1" Margin="0,15,0,20">
                        <Grid>
                            <WebView x:Name="contentView" />
                            <Rectangle x:Name="contentViewRect" />
                        </Grid>
                    </Border>
                </Grid>
            </ScrollViewer>
    
    
    
  2. Em SplitPage.xaml.cpp, modifique o código de manipulação de eventos responsável pela atualização de WebView quando ocorrem mudanças na seleção de ListView. A assinatura e a implementação da função ItemListView_SelectionChanged já estão presentes. Precisamos apenas adicionar estas linhas ao final do método:

    
    
        // Sometimes there is no selected item, e.g. when navigating back
        // from detail in logical page navigation.
        auto fi = dynamic_cast<FeedItem^>(itemListView->SelectedItem);
        if(fi != nullptr)
        {
            contentView->NavigateToString(fi->Content);
        }    
    
    
    

Hh465045.wedge(pt-br,WIN.10).gifPara adicionar um controle do WebView a DetailPage.xaml

  • Em DetailPage.xaml, precisamos vincular o texto do título ao título da postagem de blog e adicionar um controle WebView para mostrar a página do blog. Para fazer isso, substitua a Grid que contém o botão Voltar e o título da página por esta Grid e WebView:

    
    <!-- Back button and page title -->
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <AppBarButton x:Name="backButton" Height="95" Margin="10,46,0,0"
                Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}" 
                AutomationProperties.Name="Back"
                AutomationProperties.AutomationId="BackButton"
                AutomationProperties.ItemType="Navigation Button"
                Icon="Back"/>
        <TextBlock x:Name="pageTitle" Text="{Binding Title}" 
                   Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1" 
                   IsHitTestVisible="false" TextWrapping="NoWrap" 
                   VerticalAlignment="Bottom" Margin="0,0,30,40"/>
    </Grid>
    <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" 
            Grid.Row="1" Margin="120,15,20,20">
        <WebView x:Name="contentView" />
    </Border>
    
    
    

Hh465045.wedge(pt-br,WIN.10).gifPara implementar LoadState e SaveState na classe DetailPage

  1. Adicione os membros de dados privados à classe DetailPage no DetailPage.xaml.h:

    
    private:
            Platform::String^ m_itemTitle;
            Platform::String^ m_feedUri;
    
    
    
  2. Adicione o seguinte usando uma instrução para DetailPage.xaml.cpp:

    e
    
    using namespace concurrency;
    
    
  3. Em DetailPage.xaml.cpp, adicione código ao método LoadState para navegar para a postagem de blog e defina o DataContext da página. Como em SplitPage, precisamos determinar o estado anterior do aplicativo. O método atualizado tem a seguinte aparência:

    
    
    void DetailPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
    	(void) sender;	// Unused parameter
    	(void) e;	// Unused parameter
    
        // Lookup the URL for the blog title that was either
        // (a) passed to us in this session or
        // (b) saved in the SaveState method when our app was suspended.
        m_itemTitle = safe_cast<String^>(e->NavigationParameter);
    
        // We are navigating forward from SplitPage
        if (e->PageState == nullptr)
        {
            auto feedData = safe_cast<FeedData^>(App::Current->Resources->Lookup("CurrentFeed"));
            m_feedUri = feedData->Uri;
    
            auto feedItem = FeedDataSource::GetFeedItem(feedData, m_itemTitle);
            if (feedItem != nullptr)
            {
                DefaultViewModel->Insert("Title", m_itemTitle);
                // Display the web page.
                contentView->Navigate(feedItem->Link);
            }
        }
    
        // We are resuming from suspension:
        else
        {
            // We are resuming, and might not have our FeedData object yet
            // so must get it asynchronously and wait on the result.
            String^ uri = safe_cast<String^>(e->PageState->Lookup("FeedUri"));
            auto feedDataOp = FeedDataSource::GetFeedAsync(uri); //URL
            auto feedDataTask = create_task(feedDataOp);
    
            feedDataTask.then([this, e](FeedData^ feedData)
            {
                App::Current->Resources->Insert("CurrentFeed", feedData);
    
                m_feedUri = feedData->Uri;
                m_itemTitle = safe_cast<String^>(e->PageState->Lookup("Item"));
                auto feedItem = FeedDataSource::GetFeedItem(feedData, m_itemTitle);
    
                if (feedItem != nullptr)
                {
                    DefaultViewModel->Insert("Title", m_itemTitle);
                    // Display the web page.
                    contentView->Navigate(feedItem->Link);
                }
            });
        }
    }
    
    
  4. Em oposição ao LoadState, SaveState consiste em apenas duas linhas de código. Como em SplitPage, salvamos o URI do feed porque precisamos dele no LoadState para procurar o FeedItem no caso de uma continuação do encerramento. No DetailPage.xaml.cpp, use o seguinte código para substituir o método SaveState existente:

    
    
    void DetailPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
    	   (void) sender;	// Unused parameter
        // Store the itemTitle in case we are suspended or terminated.
        e->PageState->Insert("Item", m_itemTitle);
        e->PageState->Insert("FeedUri", m_feedUri);
    }
    
    
    
  5. Pressione F5 para verificar se o aplicativo é compilado e, em seguida, retorne ao Visual Studio e pressione Shift+F5 para parar a execução da compilação.

Parte 5: adicionando barras de aplicativos

Grande parte da navegação em nosso aplicativo leitor de blog ocorrerá provavelmente à medida que um usuário navega entre a página Itens e a página Dividida, e quando ele ou ela clica nas ofertas nessas páginas. Porém, na página Dividida, também devemos fornecer uma maneira para que o usuário acessar o modo de exibição detalhado da postagem de blog. Poderíamos colocar um botão em algum lugar na página, mas isso distrairia os usuários da função principal do aplicativo, que é a navegação e a leitura. Em vez disso, vamos colocar o botão em uma barra de aplicativos que ficará oculta até o usuário precisar dela. Nesta seção adicionaremos uma barra de aplicativos com um botão para navegar até a página Detalhes.

Uma barra de aplicativos é um componente da interface do usuário que fica oculto por padrão, ficando visível ou sendo ignorada quando um usuário passa o dedo pela borda da tela ou clica com o botão direito do mouse. Ela apresenta interface do usuário de navegação, comandos e ferramentas para o usuário. Uma barra de aplicativos pode aparecer na parte superior e/ou inferior da página. Recomendamos colocar a navegação na parte superior da página e as ferramentas/comandos na parte inferior.

Para adicionar uma barra de aplicativos ao XAML, atribuímos um controle AppBar à propriedade TopAppBar ou BottomAppBar de Page e inserimos um elemento AppBarButton em AppBar.

Hh465045.wedge(pt-br,WIN.10).gifPara adicionar um botão à barra de aplicativos da página Dividida

  1. Em SplitPage.xaml, cole a seguinte marcação depois da marca de fechamento Page.Resources para criar uma barra de navegação que contenha um AppBarButton que aponta para frente:

    
    
    <Page.TopAppBar>
        <AppBar Padding="10,0,10,0">
            <Grid>
               <AppBarButton x:Name="fwdButton" Height="95" Margin="150,46,0,0"
                   Command="{Binding NavigationHelper.GoForwardCommand, ElementName=pageRoot}" 
                   AutomationProperties.Name="Forward"
                   AutomationProperties.AutomationId="ForwardButton"
                   AutomationProperties.ItemType="Navigation Button"
                   HorizontalAlignment="Right"
                   Icon="Forward"/>
             </Grid>
         </AppBar>
    </Page.TopAppBar>
    
    
  2. Agora, depois do atributo Icon, mas antes de />, digite um espaço e Click="fwdButton_Click". (Você pode apenas aceitar o padrão de preenchimento automático.) Isso cria os manipuladores de eventos nas páginas code-behind.

Hh465045.wedge(pt-br,WIN.10).gifPara adicionar navegação à página Detalhes

  1. Adicione a seguinte diretiva #include a SplitPage.xaml.cpp:

    
    #include "DetailPage.xaml.h"
    
    
    
  2. Em SplitPage.xaml.cpp, adicione este código ao corpo do método fwdButton_Click:

    
    // Navigate to the appropriate destination page, and configure the new page
    // by passing required information as a navigation parameter.
    
    auto selectedItem = dynamic_cast<FeedItem^>(this->itemListView->SelectedItem);
    
    // selectedItem will be nullptr if the user invokes the app bar
    // and clicks on "view web page" without selecting an item.
    if (this->Frame != nullptr && selectedItem != nullptr)
    {
        auto itemTitle = safe_cast<String^>(selectedItem->Title);
        this->Frame->Navigate(TypeName(DetailPage::typeid), itemTitle);
    }
    
    
    

Nesse ponto, a funcionalidade básica do aplicativo está completa! Crie a funcionalidade e execute-a para testar a navegação entre páginas. Quando você escolhe um item na página Itens, o aplicativo deve navegar para a página de divisão. Na página de divisão, teste o botão Voltar para retornar à página Itens ou passe o dedo desde a parte superior da tela (ou clique com o botão direito do mouse) para invocar a barra de aplicativos e, então, toque ou clique no botão para acessar a página Detalhes. Na página Detalhes, use o botão Voltar para retornar à página de divisão. À medida que você seleciona itens diferentes na página de divisão, uma visualização do item deve aparecer. A seguir, vamos aprofundar a análise da interface do usuário e acrescentar suporte à orientação retrato.

Parte 6: adicionando animações e transições

Quando falamos sobre animações, muitas vezes pensamos em objetos saltando pela tela. Porém, no XAML, uma animação é apenas uma maneira de mudar o valor de uma propriedade em um objeto. Desse modo, as animações são úteis para muito mais do que apenas exibir objetos quicando. Em nosso aplicativo leitor de blog, adaptamos a interface do usuário para diferentes layouts e orientações usando algumas animações e a lógica de orientação internas do namespace Windows.UI.Xaml.Media.Animation

Adicionando animações de tema

Uma animação de tema é uma animação pré-configurada, por exemplo, a PopInThemeAnimation faz com que um slide seja exibido da direita para a esquerda quando a página é carregada. O aumento do valor da propriedade FromHorizontalOffset torna o efeito mais intenso. Aqui, colocamos a PopInThemeAnimation em um Storyboard e a tornamos um recurso em DetailPage.xaml. Em seguida, definimos o destino da animação de forma que ele seja o Border que envolve nosso conteúdo da Web. Isso anima a Border e tudo o que está contido nela.

Hh465045.wedge(pt-br,WIN.10).gifPara adicionar uma animação de tema à página de Detalhes

  1. Cole o seguinte trecho Extensible Application Markup Language (XAML) no nó Page.Resources em DetailPage.xaml:

    
    
    <Storyboard x:Name="PopInStoryboard">
        <PopInThemeAnimation  Storyboard.TargetName="contentViewBorder" FromHorizontalOffset="400"/>
    </Storyboard>
    
    
    
  2. Cole o seguinte código no início do método DetailPage::LoadState em DetailPage.xaml.cpp. Nossa substituição de LoadState é chamada pelo método OnNavigatedTo na classe de base LayoutAwarePage:

    
    // Run the PopInThemeAnimation. 
    Windows::UI::Xaml::Media::Animation::Storyboard^ sb = dynamic_cast<Windows::UI::Xaml::Media::Animation::Storyboard^>(this->FindName("PopInStoryboard"));
    if (sb != nullptr)
    {
        sb->Begin();
    }    
    //... rest of method as before
    
    
    

Adicionando transições de tema

Uma transição de tema é um conjunto de animações e um Storyboard que são combinados em um comportamento predefinido que podemos anexar a um elemento da interface do usuário. Uma ContentThemeTransition é usada com um ContentControl, sendo acionada automaticamente quando o conteúdo do controle muda.

No nosso aplicativo, vamos adicionar uma transição de tema ao TextBlock que contém os títulos das postagens no modo de exibição de lista da página Dividida. Quando o conteúdo do TextBlock é alterado, a ContentThemeTransition é disparada e executada automaticamente. A animação é predefinida e não precisamos fazer nada para executá-la. Basta anexá-la ao TextBlock.

Hh465045.wedge(pt-br,WIN.10).gifPara adicionar uma transição de tema à SplitPage.xaml

  • Em SplitPage.xaml, o TextBlock que é nomeado como pageTitle é uma marca de elemento vazio. Porque incorporamos um tema de transição no TextBlock, temos que alterar o TextBlock para que tenha marcas de abertura e de fechamento. Substitua a marca existente por esta marcação:

    
    
    <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{Binding Feed.Title}" Style="{StaticResource HeaderTextBlockStyle}"
        IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Bottom" Margin="0,0,30,40">
        <TextBlock.Transitions>
            <TransitionCollection>
                <ContentThemeTransition />
             </TransitionCollection>
        </TextBlock.Transitions>
    </TextBlock>
    
    
    

    Quando o conteúdo do TextBlock muda, a ContentThemeTransition é disparada, sendo executada automaticamente. A animação é predefinida e não precisamos fazer nada para executá-la. Basta anexá-la ao TextBlock. Para saber mais e obter uma lista de animações e transições de tema, veja o Guia de início rápido sobre como animar sua interface do usuário usando as animações da biblioteca.

Parte 6: usando estilos para criar uma aparência consistente

Nosso objetivo é que os usuários tenham uma experiência integrada ao se moverem entre o site Blogs da Equipe do Windows e o nosso aplicativo leitor de blog. O tema escuro padrão da nova interface do usuário do Windows não corresponde exatamente ao site Blogs da Equipe do Windows. Isso fica mais evidente na página Detalhes, onde carregamos a página real do blog em uma WebView, como mostrado aqui:

Página de detalhes com tema escuro.

Para que nosso aplicativo tenha uma aparência consistente e que possa ser atualizada quando necessário, usamos pincéis e estilos. Com um Brush, podemos definir uma aparência em um lugar e usá-la sempre que necessário. Usando Style, podemos definir valores para as propriedades de um controle e reutilizar essas configurações em todo o aplicativo.

Antes de entrarmos em detalhes, vamos ver como usar um pincel para definir a cor da tela de fundo das páginas do aplicativo. Cada página do aplicativo tem uma Grid raiz com uma propriedade Background para definir a cor da tela de fundo da página. Podemos definir a tela de fundo de cada página individualmente, desta maneira: <Grid Background="Blue"> Entretanto, uma técnica ainda melhor é definir um Brush como recurso e usá-lo para definir a cor da tela de fundo de todas as nossas páginas. Veja como isso é feito nos modelos do Visual Studio: <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">.

Isso pode ser usado para definir a propriedade Background de Grid, mas, em geral, você precisa definir mais de uma propriedade para obter a aparência desejada. Você pode agrupar configurações para qualquer número de propriedades em conjunto em um Style e aplicar Style ao controle.

O local onde um recurso é definido determina o escopo no qual ele pode ser usado. Você pode definir recursos no arquivo XAML de nível de página, no arquivo App.xaml de nível de aplicativo, ou separadamente em um arquivo XAML de dicionário de recursos. (Um arquivo XAML de dicionário de recursos pode ser compartilhado entre aplicativos, e mais de um dicionário de recursos pode ser mesclado em um aplicativo.)

Em nosso aplicativo leitor de blog, definimos recursos no App.xaml para torná-los disponíveis no aplicativo inteiro. Também temos alguns recursos definidos nos arquivos XAML para páginas individuais. Se recursos com a mesma chave forem definidos em App.xaml e em uma página, o recurso na página substituirá o recurso em App.xaml. Da mesma maneira, um recurso definido em App.xaml substituirá um recurso de mesma chave definido em um arquivo de dicionário de recursos separado. Para saber mais, veja Guia de início rápido: definindo o estilo de controles.

Em uma definição de Style, precisamos de um atributo TargetType e um conjunto de um ou mais elementos Setter. Definimos o TargetType como uma cadeia de caracteres que especifica o tipo ao qual Style é aplicado; neste caso, um Panel. Se você tentar aplicar um Style a um controle que não corresponda ao atributo TargetType, ocorrerá uma exceção. Cada elemento Setter exige uma Property e um Value. Essas configurações indicam a qual propriedade de controle a configuração se aplica, e qual é o valor a ser definido para essa propriedade.

Para mudar o Background das nossas páginas, precisamos substituir o ApplicationPageBackgroundThemeBrush pelo nosso pincel personalizado. Para o pincel personalizado, usamos o Color #FF0A2562, um azul agradável que combina com as cores do site Blogs da Equipe do Windows. Para substituir o pincel de tema do sistema, criamos um Style que se baseia em LayoutRootStyle e mudamos a propriedade Background nesse local.

Hh465045.wedge(pt-br,WIN.10).gifPara definir a cor de fundo para todas as páginas

  1. Para definir um novo estilo para a raiz do layout, cole essas definições de pincel e estilo no nó <Application.Resources> em App.xaml:

    
    
      <SolidColorBrush x:Key="WindowsBlogBackgroundBrush" Color="#FF0A2562"/>
    
      <Style x:Key="WindowsBlogLayoutRootStyle" TargetType="Panel">
          <Setter Property="Background" Value="{StaticResource WindowsBlogBackgroundBrush}"/>
      </Style>
    
    
  2. O elemento Grid raiz em cada página de nosso aplicativo usa atualmente um atributo Background para definir a cor de plano de fundo da página. Vamos alterar isso em ItemsPage.xaml, SplitPage.xaml e DetailPage.xaml para fazer referência a WindowsBlogLayoutRootStyle, que acabamos de adicionar a App.xaml, para que todas as páginas usem o mesmo plano de fundo:

    
    
     <Grid Style="{StaticResource WindowsBlogLayoutRootStyle}">
    
    
  3. Pressione F5 para compilar e executar o aplicativo e examine as páginas azuis.

Modelos de controle e de dados

Para que nosso aplicativo tenha aparência mais correspondente ao site Blogs da Equipe do Windows, usamos modelos de dados personalizados, além de Brushes e Styles.

Hh465045.wedge(pt-br,WIN.10).gifPara adicionar um modelo de controle para a data

  • No nó <Application.Resources> em App.xaml, adicione um ControlTemplate que defina um bloco quadrado que mostre a data. Definimos isso em App.xaml para que possamos fazer referência a ele em ItemsPage.xaml e em SplitPage.xaml.

    
    
     <ControlTemplate x:Key="DateBlockTemplate">
        <Canvas Height="86" Width="86"  Margin="8,8,0,8" 
            HorizontalAlignment="Left" VerticalAlignment="Top">
             <TextBlock TextTrimming="WordEllipsis" TextWrapping="NoWrap" 
                 Width="Auto" Height="Auto" Margin="8,0,4,0" 
                 FontSize="32" FontWeight="Bold">
                 <TextBlock.Text>
                     <Binding Path="PubDate" 
                     Converter="{StaticResource dateConverter}"
                         ConverterParameter="month"/>
                 </TextBlock.Text>
             </TextBlock>
    
             <TextBlock TextTrimming="WordEllipsis" TextWrapping="Wrap" 
                 Width="40" Height="Auto" Margin="8,0,0,0"
                 FontSize="34" FontWeight="Bold" Canvas.Top="36">
                 <TextBlock.Text>
                     <Binding Path="PubDate" 
                         Converter="{StaticResource dateConverter}"
                         ConverterParameter="day"/>
                 </TextBlock.Text>
             </TextBlock>
             <Line Stroke="White" StrokeThickness="2" 
                 X1="54" Y1="46" X2="54" Y2="80"/>
    
             <TextBlock TextWrapping="Wrap" 
                 Width="20" Height="Auto" 
                 FontSize="{StaticResource ControlContentThemeFontSize}"
                 Canvas.Top="42" Canvas.Left="60">
                 <TextBlock.Text>
                     <Binding Path="PubDate"
                     Converter="{StaticResource dateConverter}"
                     ConverterParameter="year"/>
                 </TextBlock.Text>
             </TextBlock>
         </Canvas>
    </ControlTemplate>
    
    
    

    Observe que esse ControlTemplate define os parâmetros —"dia," "mês" e "ano"— passados para a função Convert que criamos no DateConverter anteriormente. Como formatamos o dia, o mês e o ano como elementos independentes, cada um com um tamanho de fonte diferente, tivemos de escrever a nova funcionalidade no método Convert para retornar as partes de data separadamente.

Hh465045.wedge(pt-br,WIN.10).gifPara adicionar modelos de dados à página Itens

  1. Em ItemsPage.xaml, adicionamos esses elementos ao nó <Page.Resources> para definir a aparência dos itens de grade.

    
    
    
      <!-- light blue -->
            <SolidColorBrush x:Key="BlockBackgroundBrush" Color="#FF557EB9"/>
    
            <!-- Grid Styles -->
            <Style x:Key="GridTitleTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BaseTextBlockStyle}">
                <Setter Property="FontSize" Value="26.667"/>
                <Setter Property="Margin" Value="12,0,12,2"/>
            </Style>
    
            <Style x:Key="GridDescriptionTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BaseTextBlockStyle}">
                <Setter Property="VerticalAlignment" Value="Bottom"/>
                <Setter Property="Margin" Value="12,0,12,60"/>
            </Style>
    
            <DataTemplate x:Key="DefaultGridItemTemplate">
                <Grid HorizontalAlignment="Left" Width="250" Height="250"
                    Background="{StaticResource BlockBackgroundBrush}" >
                    <StackPanel>
                        <TextBlock Text="{Binding Title}" Style="{StaticResource GridTitleTextStyle}" Margin="5,5,5,5" />
                        <TextBlock Text="{Binding Description}" Style="{StaticResource GridDescriptionTextStyle}" Margin="5,5,5,5" />                    
                    </StackPanel>
                    <StackPanel VerticalAlignment="Bottom" Orientation="Horizontal"
                            Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                        <TextBlock Text="Last Updated" Margin="12,4,0,8" Height="42"/>
                        <TextBlock Text="{Binding PubDate, Converter={StaticResource dateConverter}}" Margin="12,4,12,8" Height="42"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>
    
        
    
    
    
  2. Também em ItemsPage.xaml, atualize o elemento GridView que está nomeado como itemGridView, para que ele faça referência ao recurso DefaultGridItemTemplate que acabamos de adicionar e para que não use o modelo embutido padrão. Substitua todo o elemento GridView (desde a marca de abertura até a marca de fechamento) por este a seguir, que consiste em uma única marca de autofechamento:

    
    
     <!-- Horizontal scrolling grid -->
            <GridView
                x:Name="itemGridView"
                AutomationProperties.AutomationId="ItemsGridView"
                AutomationProperties.Name="Items"
                TabIndex="1"
                Grid.RowSpan="2"
                Padding="116,136,116,46"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                SelectionMode="None"
                ItemTemplate="{StaticResource DefaultGridItemTemplate}"
                IsItemClickEnabled="true"
                IsSwipeEnabled="false"
                ItemClick="itemGridView_ItemClick">
            </GridView>
    
    
    

    Observe a referência à referência para nosso novo modelo de dados nesta linha: ItemTemplate="{StaticResource DefaultGridItemTemplate}".

Hh465045.wedge(pt-br,WIN.10).gifPara adicionar modelos de dados à página Dividida

  1. Em SplitPage.xaml, adicione esses elementos ao nó <Page.Resources> para ajudar a definir a aparência dos itens de lista. Observe que há dois modelos de dados, um para o modo paisagem e outro para o modo retrato:

    
    
    <!-- Green    -->
    <SolidColorBrush x:Key="BlockBackgroundBrush" Color="#FF6BBD46"/>
    
    <DataTemplate x:Name="LandscapeItemTemplate">
        <Grid HorizontalAlignment="Stretch" Width="Auto" Height="110" Margin="10,10,10,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <!-- Green date block -->
            <Border Background="{StaticResource BlockBackgroundBrush}" Width="110" Height="110" />
            <ContentControl Template="{StaticResource DateBlockTemplate}" />
            <StackPanel Grid.Column="1"  HorizontalAlignment="Left"  Margin="12,8,0,0">
                <TextBlock Text="{Binding Title}" FontSize="26.667" TextWrapping="Wrap" MaxHeight="72" Foreground="#FFFE5815" />
                    <TextBlock Text="{Binding Author}" FontSize="18.667" />
            </StackPanel>
        </Grid>
    </DataTemplate>
    
    <DataTemplate x:Name="PortraitItemTemplate">
        <Grid HorizontalAlignment="Stretch" Width="Auto" Height="110" Margin="10,10,10,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
             </Grid.ColumnDefinitions>
             <!-- Green date block -->
             <Border Background="{StaticResource BlockBackgroundBrush}" Width="110" Height="110" />
             <ContentControl Template="{StaticResource DateBlockTemplate}" />
             <StackPanel Grid.Column="1"  HorizontalAlignment="Left"  Margin="12,8,0,0">
                 <TextBlock Text="{Binding Title}" FontSize="26.667" TextWrapping="Wrap" MaxHeight="72" Foreground="#FFFE5815" />
                 <TextBlock Text="{Binding Author}" FontSize="18.667" />
             </StackPanel>
         </Grid>
    </DataTemplate>
    
    
    
  2. Em seguida, em SplitPage.xaml, precisamos atualizar a propriedade ItemTemplate no elemento ListView chamado de itemListView, para que ele use um dos modelos de dados personalizados, dependendo da orientação do dispositivo (modo paisagem ou modo retrato). Aqui, especificamos o modelo padrão no XAML e, posteriormente, aprenderemos a alternar para o modelo de modo retrato usando os elementos XAML VisualState e alguns code-behinds para detectar a mudança na orientação.

    Em SplitPage.xaml, substitua o elemento ListView existente por este:

    
    
             <!-- Vertical scrolling item list -->
            <ListView
                x:Name="itemListView"
                AutomationProperties.AutomationId="ItemsListView"
                AutomationProperties.Name="Items"
                TabIndex="1"
                Grid.Row="1"
                Margin="-10,-10,0,0"
                Padding="120,0,0,60"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                IsSwipeEnabled="False"
                SelectionChanged="ItemListView_SelectionChanged"
                ItemTemplate="{StaticResource LandscapeItemTemplate}">
    
                <ListView.ItemContainerStyle>
                    <Style TargetType="FrameworkElement">
                        <Setter Property="Margin" Value="0,0,0,10"/>
                    </Style>
                </ListView.ItemContainerStyle>
            </ListView>
    
    
    

Com os estilos aplicados, nosso aplicativo corresponde à aparência do site de Blogs da Equipe do Windows:

Página Itens com os estilos aplicados

Página Dividida com os estilos aplicados

Página Detalhes com os estilos aplicados

Usando estilos e baseando-os em outros estilos, podemos definir e aplicar rapidamente diferentes aparências a nosso aplicativo. Na próxima seção, combinamos o que sabemos sobre animações e estilos para que nosso aplicativo se adapte facilmente a vários layouts e orientações ao ser executado.

Parte 7: adaptando a diferentes layouts

Os modelos do Visual Studio para aplicativos da Windows Store não incluem um código para lidar com as mudanças na orientação de exibição. Nós mesmos temos que escrever esse código.

Para utilizar XAML a fim de realizar a transição entre modos de exibição distintos, usamos VisualStateManger para definir VisualStateGroups para cada página. Cada VisualStateGroup contém um ou mais definições de VisualState. No tempo de execução, no manipulador de eventos SizeChanged , você chama VisualStateManager::GoToState para fazer com que a página use o VisualState especificado para renderizar a interface do usuário. Nesse aplicativo, a página Itens e a página Dividida usam, cada uma, dois VisualStateGroups, um para a orientação paisagem, que é o padrão, e um para a orientação retrato. Essencialmente, um VisualState é um conjunto de transformações em elementos XAML. Cada VisualState tem animações que especificam os elementos de página a serem alterados.

Na seção Criando aparência consistente com estilos deste tutorial, criamos estilos e modelos para dar uma aparência personalizada para o nosso aplicativo. O modo de exibição de paisagem padrão usa esses estilos e modelos. Para manter a aparência personalizada no modos retrato, também precisamos criar alguns estilos e modelos personalizados para esse modo de exibição.

Hh465045.wedge(pt-br,WIN.10).gifPara adaptar o modo retrato no ItemsPage.xaml

  1. Aqui, temos que fazer uma decisão relacionada ao design. Usamos uma GridView para exibir itens no modo paisagem. Uma GridView foi projetada para rolar horizontalmente. Se quisermos que os itens se desloquem verticalmente no modo retrato, precisaríamos usar uma ListView. No entanto, se fizermos isso, o aplicativo terá que carregar dois controles separados no tempo de execução, o que pode aumentar o tempo de carregamento e o consumo de memória. (Apenas testando podemos saber se o custo de ter um controle extra é significativo.) Neste aplicativo, para manter a simplicidade, vamos usar a mesma GridView para as duas orientações. Ainda teremos a rolagem horizontal na orientação retrato, mas podemos tornar os itens um pouco mais estreitos para que sejam mais proporcionais ao retângulo do retrato. Quando fazemos isso, também temos que ajustar alguns tamanhos de fonte para evitar a quebra de texto excessiva nos títulos. Para criar este conjunto de mudanças, definimos um VisualState e o preenchemos com animações para modificar cada elemento específico.

    Em ItemsPage.xaml, adicione o seguinte estilo ao nó <Page.Resources>. Os títulos de item terão uma fonte menor, de modo que se encaixem melhor no retângulo mais estreito. Ele é consumido a partir do modelo de dados que adicionaremos na próxima etapa.

    
    <Style x:Key="GridTitlePortraitTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BaseTextBlockStyle}">
        <Setter Property="FontSize" Value="20"/>
        <Setter Property="Margin" Value="12,0,12,2"/>
    </Style>
    
    
  2. Agora, adicione este DataTemplate ao nó <Page.Resources> para definir o modelo que o GridView usará na orientação retrato:

    
    <DataTemplate x:Key="PortraitGridItemTemplate">
        <Grid HorizontalAlignment="Left" Width="160" Height="250"
        Background="{StaticResource BlockBackgroundBrush}">
            <StackPanel>
                <TextBlock Text="{Binding Title}" Style="{StaticResource GridTitlePortraitTextStyle}" Margin="5,5,5,5"/>
                <TextBlock Text="{Binding Description}" Style="{StaticResource GridDescriptionTextStyle}" Margin="5,5,5,5" TextTrimming="CharacterEllipsis"/>
            </StackPanel>
            <StackPanel VerticalAlignment="Bottom" 
                Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                <TextBlock Text="Last Updated:"/>
                <TextBlock Text="{Binding PubDate, Converter={StaticResource dateConverter}}"/>
            </StackPanel>
        </Grid>
    </DataTemplate> 
    
    
  3. Agora vamos informar a página sobre o novo modelo de dados. É aí que VisualState entra em ação. Ainda em ItemsPage.xaml, adicione este código logo acima da marca de fechamento do elemento Grid raiz:

    
     <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="ViewStates">
            <VisualState x:Name="DefaultLayout"/>
            <VisualState x:Name="Portrait">
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView" Storyboard.TargetProperty="ItemTemplate">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PortraitGridItemTemplate}"/>
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView" Storyboard.TargetProperty="Padding">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="40,136,116,40"/>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    
    

    Vamos observar este XAML por um momento. Adicionamos um elemento VisualStateManager.VisualStateGroups e aninhamos um grupo nele, ViewStates. Esse grupo contém dois estados, um chamado "DefaultLayout" para o modo paisagem e um chamado "Portrait" para modo retrato. Observe que "DefaultLayout" está vazio. Isso significa que ele é idêntico ao XAML da respectiva página. Quando o dispositivo está na orientação paisagem e a página recebe um evento SizeChanged, ele usa o XAML da página para fazer sua própria renderização. Quando o dispositivo está na orientação retrato, a página carrega o XAML padrão e faz as alterações especificadas pelas animações. A GridView usa um modelo de item diferente e também ajustamos ligeiramente o preenchimento para que as dimensões verticais se adaptem melhor à orientação retrato.

  4. Agora, adicionamos um evento SizeChanged ao elemento Page raiz digitando SizeChanged como o último atributo no elemento. Assim que você começa a digitar, a interface do usuário oferece para completar a instrução e ajudá-lo a criar um manipulador de eventos. Se você aceitar as sugestões da interface do usuário, não terá que adicionar a assinatura e a implementação do método manualmente. Para criar o manipulador de eventos, comece a digitar pageRoot_SizeChanged e aceite a sugestão de interface do usuário que corresponda a esse nome de evento.
  5. Agora temos que adicionar o code-behind para chamar o VisualState apropriado quando a orientação do dispositivo é alterada. Em ItemsPage.xaml.cpp, adicione este código ao método pageRoot_SizeChanged vazio:
    
    if (e->NewSize.Height / e->NewSize.Width >= 1)
    {
        VisualStateManager::GoToState(this, "Portrait", true);
    }
    else
    {
        VisualStateManager::GoToState(this, "DefaultLayout", true);
    }
    
    

Hh465045.wedge(pt-br,WIN.10).gifPara adaptar o modo retrato no SplitPage.xaml

  1. No modo retrato, a página Dividida trata cada uma de suas duas colunas como uma página lógica. Quando um usuário está vendo a lista, ela ocupa a tela inteira e a coluna de detalhes fica recolhida. Quando o usuário seleciona um item, a coluna de lista ;e recolhida e a coluna de detalhes ocupa a tela. Portanto, SplitPage.xaml exige dois VisualStates para o modo retrato; SinglePane mostra apenas o lado de lista e SinglePane_Detail mostra o lado de detalhes. Para cada animação, observe o destino, na propriedade de destino e o valor atribuído a essa propriedade. Esses VisualStates são incluídos no SplitPage.xaml por padrão e não serão alterados neste aplicativo. Veja também #pragma region Logical page navigation para saber como a lógica da página determina qual VisualState deve ser invocado no modo retrato.

    Vamos fazer uma alteração nesse código para que a SplitPage se comporte conforme o esperado, mesmo quando a largura de tela for 768 pixels. A função de membro UsingLogicalPageNavigation retornará falso se MinimumWidthForSupportingTwoPanes for definido como 768. Queremos que a navegação de página lógica seja invocada até mesmo nessa resolução exata, portanto, precisamos alterar o operador < na função para <=, conforme mostrado no trecho de código a seguir, ou então altere a definição de MinimumWidthForSupportingTwoPanes para 769 em SplitPage.xaml.h.

    
    bool SplitPage::UsingLogicalPageNavigation()
    {
    	return Windows::UI::Xaml::Window::Current->Bounds.Width <= MinimumWidthForSupportingTwoPanes;
    }
    
    
  2. Estranhamente, a SplitPage.xaml não especifica um manipulador de eventos SizeChanged por padrão, por isso, precisamos fazê-lo. Na marca de abertura do elemento raiz Page, comece a digitar SizeChanged e use o preenchimento automático para concluir a instrução e desfragmentar o code-behind. Depois, em SplitPage.xaml.cpp, adicione esse código ao stub de método vazio:
    
    if (e->NewSize.Height / e->NewSize.Width >= 1)
    {
        VisualStateManager::GoToState(this, "SinglePane", true);
    }
    else
    {
        VisualStateManager::GoToState(this, "PrimaryView", true);
    }
    
    

Hh465045.wedge(pt-br,WIN.10).gifPara ajustar a margem WebView no modo retrato

  1. Em DetailPage.xaml, basta ajustar a margem do nosso WebView no modo retrato para usarmos todo o espaço disponível. Para fazer isso, adicionamos dois VisualStates e, no estado "Portrait", adicionamos uma animação para alterar o valor da propriedade Margin em contentViewBorder. Cole o seguinte elemento exatamente antes da última marca Grid de fechamento:

    
    
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="ViewStates">
            <VisualState x:Name="Landscape"/>
            <VisualState x:Name="Portrait">
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentViewBorder" Storyboard.TargetProperty="Margin">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="20,5,20,20"/>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    
    
  2. Ainda no DetailPage.xaml, adicione um evento SizeChanged ao elemento Page raiz. Como antes, comece a digitar SizeChanged="pageRoot_SizeChanged" e use o preenchimento automático para desfragmentar o code-behind.
  3. Em DetailPage.xaml.cpp, adicione este código ao novo método pageRoot_SizeChanged vazio:
    
    if (e->NewSize.Height / e->NewSize.Width >= 1)
    {
        VisualStateManager::GoToState(this, "Portrait", true);
    }
    else
    {
        VisualStateManager::GoToState(this, "Landscape", true);
    }
    
    

Aprofundamento: adicionando uma tela inicial

A primeira impressão de um aplicativo tem da tela inicial é mostrada quando um usuário inicia o aplicativo. Quando o aplicativo inicializa seus recursos, a tela inicial assegura ao usuário que o aplicativo está funcionando e desaparece quando a primeira página do aplicativo está pronta para substituí-la.

A tela inicial consiste em uma cor de plano de fundo e uma imagem. Em nosso aplicativo, utilizamos apenas o plano de fundo e a imagem padrão, SplashScreen.png, que são fornecidos pelo modelo de projeto. Mas é possível alterar a cor e a imagem por meio da guia Interface de Usuário do Aplicativo no Editor de Manifesto para modificar as configurações no Package.appxmanifest.

Esta é apenas uma ideia de tela inicial para o nosso leitor de blog:

Imagem da tela inicial.

Você também pode estender a tela inicial usando as propriedades e os métodos da classe SplashScreen, por exemplo, obtendo as coordenadas da tela inicial e usando-as para inserir a primeira página do aplicativo. Também é possível descobrir quando a tela inicial é ignorada; assim, você sabe que é hora de iniciar as animações de entrada de conteúdo para o aplicativo.

Próxima etapa

Este tutorial ensina a usar modelos de página internos do Microsoft Visual Studio Express 2012 para Windows 8 para criar um aplicativo multipágina e como navegar e passar dados entre as páginas. Aprendemos a usar estilos e modelos para adequar nosso aplicativo à personalidade do site Blogs da Equipe do Windows. Também aprendemos a usar animações de tema e uma barra de aplicativos para que nosso aplicativo tenha o nível da personalidade de um aplicativo da Windows Store. Finalmente, aprendemos a adaptar nosso aplicativo a diversos layouts e orientações para que sempre apresente seu melhor.

Nosso aplicativo está quase pronto para ser enviado à Windows Store. Para saber mais sobre como enviar um aplicativo à Windows Store, veja:

Tópicos relacionados

Mapa de aplicativos do Tempo de Execução do Windows em C++
Desenvolver aplicativos da Windows Store usando o Visual Studio 11

 

 

Mostrar:
© 2014 Microsoft