Creare un'app UWP lettore di blog (C++)

Dall'inizio alla fine, ecco come usare C++ e XAML per sviluppare un'app UWP da distribuire in Windows 10. L'app legge i blog da feed RSS 2.0 o Atom 1.0.

Questa esercitazione si basa sul presupposto che l'utente abbia familiarità con i concetti illustrati in Creare la prima app di Windows Store in C++.

Per studiare la versione finita di questa app, puoi scaricarla dal sito Web della raccolta di esempi di codice MSDN.

Per questa esercitazione useremo Visual Studio Community 2015 o versione successiva. Se usi un'altra edizione di Visual Studio, i comandi di menu potrebbero essere leggermente diversi.

Per esercitazioni relative ad altri linguaggi di programmazione, vedi:

Obiettivi

Questa esercitazione offre informazioni su come creare un'app di Windows Store costituita da più pagine e su come e quando usare le estensioni del componente Visual C++ (C++/CX) per semplificare la scrittura di codice in Windows Runtime. Indica inoltre come applicare la classe concurrency::task per usare le API di Windows Runtime asincrone.

L'app SimpleBlogReader include queste funzionalità:

  • Accesso a dati di feed RSS e Atom tramite Internet.
  • Visualizzazione di un elenco di feed e titoli di feed.
  • Due modi per leggere un post, come testo semplice o come pagina Web.
  • Supporto di Gestione del ciclo di vita dei processi, con salvataggio e ricaricamento corretti dello stato se il sistema si arresta mentre è in esecuzione un'altra attività in primo piano.
  • Adattamento a diversi formati di finestre e orientamenti del dispositivo (orizzontale o verticale).
  • Possibilità di aggiungere e rimuovere i feed.

Parte 1: impostare il progetto

Per iniziare useremo il modello App vuota (Windows universale) di C++ per creare un progetto.

Hh465045.wedge(it-it,WIN.10).gifPer creare un nuovo progetto

  • In Visual Studio scegli File > Nuovo > Progetto, seleziona Installato > Visual C++ > Windows > Universale. Nel riquadro centrale seleziona il modello App vuota (Windows universale). Assegna alla soluzione il nome "SimpleBlogReader". Per istruzioni più complete, vedi Creare un'app "Hello World" (C++).

Iniziamo aggiungendo tutte le pagine. È più facile procedere in questo modo per tutte le pagine contemporaneamente, perché quando si inizia la scrittura di codice, in ogni pagina è necessario specificare #include per la pagina di destinazione dello spostamento.

Hh465045.wedge(it-it,WIN.10).gifAggiungere le pagine per l'app di Windows

  1. In effetti iniziamo distruggendo. Fai clic con il pulsante destro del mouse su MainPage.xaml, scegli Rimuovi e quindi fai clic su Elimina per eliminare definitivamente il file e i relativi file code-behind. Questo è un tipo di pagina vuota che non include il supporto per la navigazione di cui abbiamo bisogno. A questo punto, fai clic con il pulsante destro del mouse sul nodo del progetto e scegli Aggiungi > Nuovo elemento. Aggiungere un nuovo elemento in Visual C++
  2. Nel riquadro sinistro scegli XAML e nel riquadro centrale scegli Pagina elementi. Assegna il nome MainPage.xaml e fai clic su OK. Vedrai una finestra di messaggio che chiede se è possibile aggiungere alcuni nuovi file al progetto. Fai clic su . Nel nostro codice di avvio è necessario fare riferimento alle classi SuspensionManager e NavigationHelper definite in questi file, che Visual Studio posiziona in una nuova cartella Common.
  3. Aggiungi un controllo SplitPage e accetta il nome predefinito.
  4. Aggiungi un controllo BasicPage e chiamalo WebViewerPage.

Aggiungeremo gli elementi dell'interfaccia utente in queste pagine in un secondo momento.

Hh465045.wedge(it-it,WIN.10).gifAggiungere le pagine per l'app di Windows Phone

  1. In Esplora soluzioni espandi il progetto Windows Phone 8.1. Fai clic con il pulsante destro del mouse su MainPage.xaml e scegli Rimuovi > Elimina definitivamente.
  2. Aggiungi un nuovo elemento XAML Pagina base e chiamalo MainPage.xaml. Fai clic su proprio come hai fatto per il progetto Windows.
  3. Forse noterai che la gamma di modelli di pagina è più limitata nel progetto per Windows Phone. In questa app usiamo solo pagine di base. Aggiungi altre tre pagine di base e chiamale FeedPage, TextViewerPage e WebViewerPage.

Parte 2: creare un modello di dati

Le app di Store basate su modelli di Visual Studio rispecchiano in modo non rigoroso un'architettura MVVM. Nella nostra app, il modello è costituito da classi che incapsulano feed di blog. Ogni pagina XAML nell'app rappresenta una particolare visualizzazione dei dati e ogni classe di pagina ha un modello di visualizzazione proprio che è una proprietà denominata DefaultViewModel ed è di tipo Map<String^,Object^>. La mappa archivia i dati associati ai controlli XAML nella pagina e funge da contesto dei dati per la pagina.

Il nostro modello è costituito da tre classi. La classe FeedData rappresenta l'URI di primo livello e i metadati per un feed del blog. Il feed all'indirizzo https://blogs.windows.com/windows/b/buildingapps/rss.aspx è un esempio di ciò che incapsula FeedData. Un feed include un elenco di post di blog, che noi rappresentiamo come oggetti FeedItem. Ogni FeedItem rappresenta un post e contiene il titolo, il contenuto, l'URI e altri metadati. Il post all'indirizzo https://blogs.windows.com/windows/b/buildingapps/archive/2014/05/28/using-the-windows-phone-emulator-for-testing-apps-with-geofencing.aspx è un esempio di FeedItem. La prima pagina nella nostra app è una visualizzazione dei feed, la seconda pagina è una visualizzazione di FeedItem per un singolo feed e le ultime due pagine forniscono diverse visualizzazioni di un singolo post: come testo normale o come pagina Web.

La classe FeedDataSource contiene una raccolta di elementi FeedData insieme ai metodi per il loro download.

Per ricapitolare:

  • La classe FeedData contiene le informazioni su un feed RSS o Atom.

  • La classe FeedItem contiene le informazioni sui singoli post di blog inclusi nel feed.

  • La classe FeedDataSource contiene i metodi per scaricare i feed e inizializzare le classi di dati.

Definiamo queste classi come classi ref pubbliche per consentire il data binding. I controlli XAML non possono interagire con le classi C++ standard. Usiamo l'attributo Bindable per indicare al compilatore XAML che stiamo eseguendo il binding dinamico a istanze di questi tipi. In una classe ref pubblica, i membri dati pubblici vengono esposti come proprietà. Per le proprietà senza una logica speciale non servono un getter e un setter specificati dall'utente, perché verranno forniti dal compilatore. Nella classe FeedData nota come viene usato Windows::Foundation::Collections::IVector per esporre un tipo di raccolta pubblico. Usiamo la classe Platform::Collections::Vector internamente come il tipo concreto che implementa IVector.

Entrambi i progetti Windows e Windows Phone useranno lo stesso modello di dati, quindi metteremo le classi nel progetto condiviso.

Hh465045.wedge(it-it,WIN.10).gifPer creare classi di dati personalizzate

  1. In Esplora soluzioni, nel menu di scelta rapida del nodo del progetto SimpleBlogReader.Shared scegli Aggiungi > Nuovo elemento. Seleziona l'opzione File di intestazione (.h) e assegna il nome FeedData.h.

  2. Apri FeedData.h e quindi incolla il codice seguente nel file. Nota la direttiva #include per "pch.h", ovvero l'intestazione precompilata dove vengono posizionate le intestazioni di sistema che non cambiano molto o affatto. Per impostazione predefinita, pch.h include collection.h, necessaria per il tipo Platform::Collections::Vector e ppltasks.h, necessaria per concurrency::task e i tipi correlati. Queste intestazioni includono sia i valori <string> che <vector> necessari per l'app, quindi non è necessario includerli in modo esplicito.

    //feeddata.h
    
    #pragma once
    #include "pch.h"
    
    namespace SimpleBlogReader
    {
    
        namespace WFC = Windows::Foundation::Collections;
        namespace WF = Windows::Foundation;
        namespace WUIXD = Windows::UI::Xaml::Documents;
        namespace WWS = Windows::Web::Syndication;
    
    
        /// <summary>
        /// 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.
        /// </summary>
        [Windows::UI::Xaml::Data::Bindable]
        public ref class FeedItem sealed
        {
        public:
            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){}
        };
    
        /// <summary>
        /// A FeedData object represents a feed that contains 
        /// one or more FeedItems. 
        /// </summary>
        [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 WFC::IVector<FeedItem^>^ Items
            {
                WFC::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;
        };
    }
    

    Le classi sono classi ref perché le classi XAML di Windows Runtime devono interagire con esse per il data binding con l'interfaccia utente. Anche l'attributo [Bindable] in tali classi è necessario per il data binding. Il meccanismo di binding non le vedrebbe senza questo attributo.

Parte 3: scaricare i dati

La classe FeedDataSource contiene metodi per il download dei feed e anche altri metodi helper. Contiene anche la raccolta dei feed scaricati che viene aggiunta al valore "Items" in DefaultViewModel della pagina principale dell'app. FeedDataSource usa la classe Windows::Web::Syndication::SyndicationClient per eseguire il download. Dato che le operazioni di rete possono richiedere tempo, queste operazioni sono asincrone. Al termine del download di un feed, l'oggetto FeedData viene inizializzato e aggiunto alla raccolta FeedDataSource::Feeds. Si tratta di un IObservable<T> che indica che l'interfaccia utente riceverà notifica dell'aggiunta di un elemento e lo visualizzerà nella pagina principale. Per le operazioni asincrone usiamo la classe concurrency::task e le classi e i metodi correlati da ppltasks.h. La funzione create_task viene usata per eseguire il wrapping delle chiamate di funzione IAsyncOperation e IAsyncAction nell'API di Windows. La funzione membro task::then viene usata per eseguire codice che deve attendere fino a quando l'attività viene completata.

Una funzionalità interessante dell'app è che l'utente non deve attendere il completamento del download di tutti i feed. Può fare clic su un feed non appena viene visualizzato e passare a una nuova pagina che visualizza tutti gli elementi per tale feed. Ecco un esempio di un'interfaccia utente "veloce e fluida" resa possibile grazie all'esecuzione di molte operazioni nei thread in background. La vedremo in azione dopo aver aggiunto la pagina XAML principale.

Tuttavia, le operazioni asincrone possono aggiungere complessità, perché "veloce e fluido" non significa anche "gratis". Se hai letto le esercitazioni precedenti, sai che un'app non attiva può essere terminata dal sistema per liberare memoria e quindi ripristinata quando l'utente vi ritorna. Nella nostra app, non salviamo tutti i dati dei feed alla chiusura, perché l'operazione richiederebbe una grande quantità di spazio di archiviazione e alla fine i dati potrebbero risultare non aggiornati. I feed vengono sempre scaricati all'avvio. Questo significa però che dobbiamo tenere conto dello scenario in cui l'app viene ripresa dalla chiusura e tenta immediatamente di visualizzare un oggetto FeedData per il quale il download non è ancora stato completato. Dobbiamo assicurarci di non tentare di visualizzare i dati fino a quando non sono disponibili. In questo caso non possiamo usiamo il metodo then, ma possiamo usare un task_completion_event. Questo evento impedirà tentativi di accesso a un oggetto FeedData da qualsiasi codice fino al completamento del caricamento di tale oggetto.

Hh465045.wedge(it-it,WIN.10).gif

  1. Aggiungi la classe FeedDataSource a FeedData.h. come parte dello spazio dei nomi SimpleBlogReader:

        /// <summary>
        /// A FeedDataSource represents a collection of FeedData objects
        /// and provides the methods to retrieve the stores URLs and 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" /> 
        /// </summary>
        [Windows::UI::Xaml::Data::Bindable]
        public ref class FeedDataSource sealed
        {
        private:
            Platform::Collections::Vector<FeedData^>^ m_feeds;
            FeedData^ GetFeedData(Platform::String^ feedUri, WWS::SyndicationFeed^ feed);
            concurrency::task<WFC::IVector<Platform::String^>^> GetUserURLsAsync();
            void DeleteBadFeedHandler(Windows::UI::Popups::UICommand^ command);
    
        public:
            FeedDataSource();
            property Windows::Foundation::Collections::IObservableVector<FeedData^>^ Feeds
            {
                Windows::Foundation::Collections::IObservableVector<FeedData^>^ get()
                {
                    return this->m_feeds;
                }
            }
            property Platform::String^ CurrentFeedUri;
            void InitDataSource();        
    
        internal:
            // This is used to prevent SplitPage from prematurely loading the last viewed page on resume.
            concurrency::task_completion_event<FeedData^> m_LastViewedFeedEvent;
            concurrency::task<void> RetrieveFeedAndInitData(Platform::String^ url, WWS::SyndicationClient^ client);
        };
    
  2. Ora crea un file denominato FeedData.cpp nel progetto condiviso e incolla questo codice:

    #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::Foundation::Collections;
    using namespace Windows::Web::Syndication;
    using namespace Windows::Storage;
    using namespace Windows::Storage::Streams;
    
    FeedDataSource::FeedDataSource()
    {
           m_feeds = ref new Vector<FeedData^>();
           CurrentFeedUri = "";
    }
    
    ///<summary>
    /// Uses SyndicationClient to get the top-level feed object, then initializes 
    /// the app's data structures. In the case of a bad feed URL, the exception is 
    /// caught and the user can permanently delete the feed.
    ///</summary>
    task<void> FeedDataSource::RetrieveFeedAndInitData(String^ url, SyndicationClient^ client)
    {
           // Create the async operation. feedOp is an 
           // IAsyncOperationWithProgress<SyndicationFeed^, RetrievalProgress>^
           auto feedUri = ref new Uri(url);
           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 pass to the continuation. The continuation can run
           // on any thread.
           return create_task(feedOp).then([this, url](SyndicationFeed^ feed) -> FeedData^
           {
                  return GetFeedData(url, feed);
           }, concurrency::task_continuation_context::use_arbitrary())
    
                  // Append the initialized FeedData object to the items collection.
                  // This has to happen on the UI thread. By default, a .then
                  // continuation runs in the same apartment that it was called on.
                  // We can append safely to the Vector from multiple threads
                  // without taking an explicit lock.
                  .then([this, url](FeedData^ fd)
           {
                  if (fd->Uri == CurrentFeedUri)
                  {
                         // By setting the event we tell the resuming SplitPage the data
                         // is ready to be consumed.
                         m_LastViewedFeedEvent.set(fd);
                  }
    
                  m_feeds->Append(fd);
    
           })
    
                  // The last continuation serves as an error handler.
                  // get() will surface any unhandled exceptions in this task chain.
                  .then([this, url](task<void> t)
           {
                  try
                  {
                         t.get();
                  }
    
                  catch (Platform::Exception^ e)
                  {
                         // Sometimes a feed URL changes(I'm talking to you, Windows blogs!)
                         // When that happens, or when the users pastes in an invalid URL or a 
                         // URL is valid but the content is malformed somehow, an exception is 
                         // thrown in the task chain before the feed is added to the Feeds 
                         // collection. The only recourse is to stop trying to read the feed.
                         // That means deleting it from the feeds.txt file in local settings.
                         SyndicationErrorStatus status = SyndicationError::GetStatus(e->HResult);
                         String^ msgString;
    
                         // Define the action that will occur when the user presses the popup button.
                         auto handler = ref new Windows::UI::Popups::UICommandInvokedHandler(
                               [this, url](Windows::UI::Popups::IUICommand^ command)
                         {
                               auto app = safe_cast<App^>(App::Current);
                               app->DeleteUrlFromFeedFile(url);
                         });
    
                         // Display a message that hopefully is helpful.
                         if (status == SyndicationErrorStatus::InvalidXml)
                         {
                               msgString = "There seems to be a problem with the formatting in this feed: ";
                         }
    
                         if (status == SyndicationErrorStatus::Unknown)
                         {
                               msgString = "I can't load this feed (is the URL correct?): ";
                         }
    
                         // Show the popup.
                         auto msg = ref new Windows::UI::Popups::MessageDialog(
                               msgString + url);
                         auto cmd = ref new Windows::UI::Popups::UICommand(
                               ref new String(L"Forget this feed."), handler, 1);
                         msg->Commands->Append(cmd);
                         msg->ShowAsync();
                  }
           }); //end task chain
    }
    
    ///<summary>
    /// Retrieve the data for each atom or rss feed and put it into our custom data structures.
    ///</summary>
    void FeedDataSource::InitDataSource()
    {
           // Hard code some feeds for now. Later in the tutorial we'll improve this.
           auto urls = ref new Vector<String^>();
           urls->Append(L"http://sxp.microsoft.com/feeds/3.0/devblogs");
           urls->Append(L"https://blogs.windows.com/windows/b/bloggingwindows/rss.aspx");
           urls->Append(L"https://azure.microsoft.com/blog/feed");
    
           // Populate the list of feeds.
           SyndicationClient^ client = ref new SyndicationClient();
           for (auto url : urls)
           {
                  RetrieveFeedAndInitData(url, client);
           }
    }
    
    ///<summary>
    /// Creates our app-specific representation of a FeedData.
    ///</summary>
    FeedData^ FeedDataSource::GetFeedData(String^ feedUri, SyndicationFeed^ feed)
    {
           FeedData^ feedData = ref new FeedData();
    
           // Store the Uri now in order to map completion_events 
           // when resuming from termination.
           feedData->Uri = feedUri;
    
           // Get the title of the feed (not the individual posts).
           // auto app = safe_cast<App^>(App::Current);
           TextHelper^ helper = ref new TextHelper();
    
           feedData->Title = helper->UnescapeText(feed->Title->Text);
           if (feed->Subtitle != nullptr)
           {
                  feedData->Description = helper->UnescapeText(feed->Subtitle->Text);
           }
    
           // Occasionally a feed might have no posts, so we guard against that here.
           if (feed->Items->Size > 0)
           {
                  // Use the date of the latest post as the last updated date.
                  feedData->PubDate = feed->Items->GetAt(0)->PublishedDate;
    
                  for (auto item : feed->Items)
                  {
                         FeedItem^ feedItem;
                         feedItem = ref new FeedItem();
                         feedItem->Title = helper->UnescapeText(item->Title->Text);
                         feedItem->PubDate = item->PublishedDate;
    
                         //Only get first author in case of multiple entries.
                         item->Authors->Size > 0 ? feedItem->Author =
                               item->Authors->GetAt(0)->Name : feedItem->Author = L"";
    
                         if (feed->SourceFormat == SyndicationFormat::Atom10)
                         {
                               // Sometimes a post has only the link to the web page
                               if (item->Content != nullptr)
                               {
                                      feedItem->Content = helper->UnescapeText(item->Content->Text);
                               }
                               feedItem->Link = ref new Uri(item->Id);
                         }
                         else
                         {
                               feedItem->Content = item->Summary->Text;
                               feedItem->Link = item->Links->GetAt(0)->Uri;
                         }
                         feedData->Items->Append(feedItem);
                  };
           }
           else
           {
                  feedData->Description = "NO ITEMS AVAILABLE." + feedData->Description;
           }
    
           return feedData;
    
    } //end GetFeedData
    
  3. Recuperiamo ora un'istanza di FeedDataSource nella nostra app. In app.xaml.h aggiungi una direttiva #include per FeedData.h per rendere visibili i tipi.

        #include "FeedData.h"
    
    • Nel progetto condiviso, in App.xaml, aggiungi un nodo Application.Resources e inserisci un riferimento a FeedDataSource in modo che la pagina abbia ora questo aspetto:

          <Application
              x:Class="SimpleBlogReader.App"
              xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:local="using:SimpleBlogReader">
      
              <Application.Resources>
                  <local:FeedDataSource x:Key="feedDataSource" />    
              </Application.Resources>
      </Application>
      

      Questo markup causerà la creazione di un oggetto FeedDataSource all'avvio dell'app e l'oggetto è accessibile da qualsiasi pagina nell'app. Quando viene generato l'evento OnLaunched, l'oggetto App chiama InitDataSource per fare in modo che l'istanza di FeedDataSource inizi il download di tutti i relativi dati.

      Il progetto non può ancora essere compilato perché dobbiamo aggiungere alcune definizioni di classe aggiuntive.

Parte 4: gestire la sincronizzazione dei dati alla ripresa dalla chiusura

Al primo avvio dell'app e mentre l'utente si sposta tra le pagine, non è richiesta la sincronizzazione dell'accesso ai dati. I feed vengono visualizzati nella prima pagina solo dopo l'inizializzazione e le altre pagine non tentano mai di accedere ai dati finché l'utente non fa clic su un feed visibile. E in seguito, tutti gli accessi sono in sola lettura, perché i dati di origine non vengono mai modificati. Esiste tuttavia uno scenario che richiede la sincronizzazione: quando l'app viene terminata mentre è attiva una pagina basata su un particolare feed, tale pagina dovrà rieseguire il binding ai dati di tale feed alla ripresa dell'app. In questo caso è possibile che una pagina tenti di accedere a dati che non esistono ancora. È quindi necessario un modo per forzare la pagina ad aspettare che i dati siano pronti.

Le funzioni seguenti consentono all'app di ricordare quale feed era visualizzato. Il metodo SetCurrentFeed si limita a salvare il feed nelle impostazioni locali da cui può essere recuperato anche dopo che l'app esce dalla memoria. Il metodo GetCurrentFeedAsync è quello interessante, perché dobbiamo assicurarci che quando torniamo e vogliamo ricaricare l'ultimo feed, questa operazione non venga tentata prima che il feed sia stato ricaricato. Parleremo più in dettaglio di questo codice più avanti. Aggiungeremo il codice alla classe App perché lo chiameremo sia dall'app di Windows che dall'app di Windows Phone.

  1. Aggiungi queste firme di metodo in app.xaml.h. L'accessibilità interna significa che possono essere usati solo da altro codice C++ nello stesso spazio dei nomi.

        internal:
        concurrency::task<FeedData^> GetCurrentFeedAsync();
        void SetCurrentFeed(FeedData^ feed); 
        FeedItem^ GetFeedItem(FeedData^ fd, Platform::String^ uri);
        void AddFeed(Platform::String^ feedUri);
        void RemoveFeeds(Platform::Collections::Vector<FeedData^>^ feedsToDelete);
        void DeleteUrlFromFeedFile(Platform::String^ s);
    
  2. All'inizio del file app.xaml.cpp aggiungi le seguenti istruzioni using.

        using namespace concurrency;
        using namespace Platform::Collections;
        using namespace Windows::Storage;
    

    Devi disporre dello spazio dei nomi concurrency per l'attività, dello spazio dei nomi Platform::Collections per Vector e dello spazio dei nomi Windows::Storage per ApplicationData.

    Aggiungi queste righe alla fine del file:

    ///<summary>
    /// Grabs the URI that the user entered, then inserts it into the in-memory list
    /// and retrieves the data. Then adds the new feed to the data file so it's 
    /// there the next time the app starts up.
    ///</summary>
    void App::AddFeed(String^ feedUri)
    {
        auto feedDataSource =
            safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
        auto client = ref new Windows::Web::Syndication::SyndicationClient();
    
        // The UI is data-bound to the items collection and will update automatically
        // after we append to the collection.
        create_task(feedDataSource->RetrieveFeedAndInitData(feedUri, client))
            .then([this, feedUri] {
    
            // Add the uri to the roaming data. The API requires an IIterable so we have to 
            // put the uri in a Vector.
            Vector<String^>^ vec = ref new Vector<String^>();
            vec->Append(feedUri);
            concurrency::create_task(ApplicationData::Current->LocalFolder->
                CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
                .then([vec](StorageFile^ file)
            {
                FileIO::AppendLinesAsync(file, vec);
            });
        });
    }
    
    /// <summary>
    /// Called when the user chooses to remove some feeds which otherwise
    /// are valid Urls and currently are displaying in the UI, and are stored in 
    /// the Feeds collection as well as in the feeds.txt file.
    /// </summary>
    void App::RemoveFeeds(Vector<FeedData^>^ feedsToDelete)
    {
        // Create a new list of feeds, excluding the ones the user selected.
        auto feedDataSource =
            safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));  
    
        // If we delete the "last viewed feed" we need to also remove the reference to it
        // from local settings.
        ApplicationDataContainer^ localSettings = ApplicationData::Current->LocalSettings;
        String^ lastViewed;
    
        if (localSettings->Values->HasKey("LastViewedFeed"))
        {
            lastViewed =
                safe_cast<String^>(localSettings->Values->Lookup("LastViewedFeed"));
        }
    
        // When performance is an issue, consider using Vector::ReplaceAll
        for (const auto& item : feedsToDelete)
        {
            unsigned int index = -1;
            bool b = feedDataSource->Feeds->IndexOf(item, &index);
            if (index >= 0)
            {
                feedDataSource->Feeds->RemoveAt(index);           
            }
    
            // Prevent ourself from trying later to reference 
            // the page we just deleted.
            if (lastViewed != nullptr && lastViewed == item->Title)
            {
                localSettings->Values->Remove("LastViewedFeed");
            }
        }
    
        // Re-initialize feeds.txt with the new list of URLs.
        Vector<String^>^ newFeedList = ref new Vector<String^>();
        for (const auto& item : feedDataSource->Feeds)
        {
            newFeedList->Append(item->Uri);
        }
    
        // Overwrite the old data file with the new list.
        create_task(ApplicationData::Current->LocalFolder->
            CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
            .then([newFeedList](StorageFile^ file)
        {
            FileIO::WriteLinesAsync(file, newFeedList);
        });
    }
    
    
    ///<summary>
    /// This function enables the user to back out after
    /// entering a bad url in the "Add Feed" text box, for example pasting in a 
    /// partial address. This function will also be called if a URL that was previously 
    /// formatted correctly one day starts returning malformed XML when we try to load it.
    /// In either case, the FeedData was not added to the Feeds collection, and so 
    /// we only need to delete the URL from the data file.
    /// </summary>
    void App::DeleteUrlFromFeedFile(Platform::String^ s)
    {
        // Overwrite the old data file with the new list.
        create_task(ApplicationData::Current->LocalFolder->
            CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
            .then([this](StorageFile^ file)
        {
            return FileIO::ReadLinesAsync(file);
        }).then([this, s](IVector<String^>^ lines)
        {
            for (unsigned int i = 0; i < lines->Size; ++i)
            {
                if (lines->GetAt(i) == s)
                {
                    lines->RemoveAt(i);
                }
            }
            return lines;
        }).then([this](IVector<String^>^ lines)
        {
            create_task(ApplicationData::Current->LocalFolder->
                CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
                .then([this, lines](StorageFile^ file)
            {
                FileIO::WriteLinesAsync(file, lines);
            });
        });
    }
    
    ///<summary>
    /// Returns the feed that the user last selected from MainPage.
    ///<summary>
    task<FeedData^> App::GetCurrentFeedAsync()
    {
        FeedDataSource^ feedDataSource = 
            safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
        return create_task(feedDataSource->m_LastViewedFeedEvent);
    }
    
    ///<summary>
    /// So that we can always get the current feed in the same way, we call this 
    // method from ItemsPage when we change the current feed. This way the caller 
    // doesn't care whether we're resuming from termination or new navigating.
    // The only other place we set the event is in InitDataSource in FeedData.cpp 
    // when resuming from termination.
    ///</summary>
    
    void App::SetCurrentFeed(FeedData^ feed)
    {
        // Enable any pages waiting on the FeedData to continue
        FeedDataSource^ feedDataSource = 
            safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
        feedDataSource->m_LastViewedFeedEvent = task_completion_event<FeedData^>();
        feedDataSource->m_LastViewedFeedEvent.set(feed);
    
        // Store the current URI so that we can look up the correct feedData object on resume.
        ApplicationDataContainer^ localSettings = 
            ApplicationData::Current->LocalSettings;
        auto values = localSettings->Values;
        values->Insert("LastViewedFeed", 
            dynamic_cast<PropertyValue^>(PropertyValue::CreateString(feed->Uri)));
    }
    
    // We stored the string ID 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^ App::GetFeedItem(FeedData^ fd, String^ uri)
    {
        auto items = fd->Items;
        auto itEnd = end(items);
        auto it = std::find_if(begin(items), itEnd,
            [uri](FeedItem^ fi)
        {
            return fi->Link->AbsoluteUri == uri;
        });
    
        if (it != itEnd)
            return *it;
    
        return nullptr;
    }
    

Parte 5: convertire i dati in forme usabili

Non tutti i dati non elaborati sono necessariamente in una forma utilizzabile. Un feed RSS o Atom esprime la data di pubblicazione come valore numerico RFC 822. Dobbiamo trovare il modo di convertire questo tipo di valore in un testo comprensibile dall'utente. A tale scopo, creeremo una classe personalizzata che implementa IValueConverter e accetta un valore RFC 833 come stringhe di input e output per ogni componente della data. Successivamente, nel codice XAML che visualizza i dati, eseguiremo il binding all'output della nostra classe DateConverter anziché al formato dei dati non elaborati.

Hh465045.wedge(it-it,WIN.10).gifAggiungere un convertitore di date

  1. Nel progetto condiviso crea un nuovo file con estensione h e aggiungi questo codice:

    //DateConverter.h
    
    #pragma once
    #include <string> //for wcscmp
    #include <regex>
    
    namespace SimpleBlogReader
    {
        namespace WGDTF = Windows::Globalization::DateTimeFormatting;
    
        /// <summary>
        /// Implements IValueConverter so that we can convert the numeric date
        /// representation to a set of strings.
        /// </summary>
        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 = WGDTF::DateTimeFormatter::ShortDate::get();
                    result = dtf->Format(dt);
                }
                else if (wcscmp(param->Data(), L"month") == 0)
                {
                    auto formatter =
                        ref new WGDTF::DateTimeFormatter("{month.abbreviated(3)}");
                    result = formatter->Format(dt);
                }
                else if (wcscmp(param->Data(), L"day") == 0)
                {
                    auto formatter =
                        ref new WGDTF::DateTimeFormatter("{day.integer(2)}");
                    result = formatter->Format(dt);
                }
                else if (wcscmp(param->Data(), L"year") == 0)
                {
                    auto formatter =
                        ref new WGDTF::DateTimeFormatter("{year.full}");
                    auto tempResult = formatter->Format(dt); //e.g. "2014"
    
                    // Insert a hard return after second digit to get the rendering 
                    // effect we want
                    std::wregex r(L"(\\d\\d)(\\d\\d)");
                    result = ref new Platform::String(
                        std::regex_replace(tempResult->Data(), r, L"$1\n$2").c_str());
                }
                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();
            }
        };
    }
    
  2. Includilo ora in App.xaml.h con #include:

    #include "DateConverter.h"
    
  3. Creane poi un'istanza in App.xaml nel nodo Application.Resources:

    <local:DateConverter x:Key="dateConverter" />
    

Il contenuto del feed viene trasmesso in formato HTML o in alcuni casi come testo in formato XML. Per visualizzare il contenuto in un RichTextBlock dobbiamo convertirlo in formato RTF. La classe seguente usa la funzione di Windows HtmlUtilities per analizzare il codice HTML e quindi usa funzioni <regex> per suddividere il contenuto in paragrafi, in modo da poter creare oggetti di testo RTF. Non possiamo usare il data binding in questo scenario, perciò non è necessario che la classe implementi IValueConverter. Ci limiteremo a creare istanze locali della classe nelle pagine in cui ne abbiamo bisogno.

Hh465045.wedge(it-it,WIN.10).gifAggiungere un convertitore di testo

  1. Nel progetto condiviso aggiungi un nuovo file con estensione h, chiamalo TextHelper.h e aggiungi questo codice:

    #pragma once
    
    namespace SimpleBlogReader
    {
        namespace WFC = Windows::Foundation::Collections;
        namespace WF = Windows::Foundation;
        namespace WUIXD = Windows::UI::Xaml::Documents;
    
        public ref class TextHelper sealed
        {
        public:
            TextHelper();
            WFC::IVector<WUIXD::Paragraph^>^ CreateRichText(
                Platform::String^ fi,
                WF::TypedEventHandler < WUIXD::Hyperlink^,
                WUIXD::HyperlinkClickEventArgs^ > ^ context);
    
            Platform::String^ UnescapeText(Platform::String^ inStr);
    
        private:
    
            std::vector<std::wstring> SplitContentIntoParagraphs(const std::wstring& s, 
                const std::wstring& rgx);
            std::wstring UnescapeText(const std::wstring& input);
    
            // Maps some HTML entities that we'll use to replace the escape sequences
            // in the call to UnescapeText when we create feed titles and render text. 
            std::map<std::wstring, std::wstring> entities;
        };
    }
    
  2. Aggiungi ora TextHelper.cpp:

    #include "pch.h"
    #include "TextHelper.h"
    
    using namespace std;
    using namespace SimpleBlogReader;
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    
    using namespace Windows::Data::Html;
    using namespace Windows::UI::Xaml::Documents;
    
    /// <summary>
    /// Note that in this example we don't map all the possible HTML entities. Feel free to improve this.
    /// Also note that we initialize the map like this because VS2013 Udpate 3 does not support list
    /// initializers in a member declaration.
    /// </summary>
    TextHelper::TextHelper() : entities(
        {
            { L"&#60;", L"<" }, { L"&#62;", L">" }, { L"&#38;", L"&" }, { L"&#162;", L"¢" }, 
            { L"&#163;", L"£" }, { L"&#165;", L"¥" }, { L"&#8364;", L"€" }, { L"&#8364;", L"©" },
            { L"&#174;", L"®" }, { L"&#8220;", L"“" }, { L"&#8221;", L"”" }, { L"&#8216;", L"‘" },
            { L"&#8217;", L"’" }, { L"&#187;", L"»" }, { L"&#171;", L"«" }, { L"&#8249;", L"‹" },
            { L"&#8250;", L"›" }, { L"&#8226;", L"•" }, { L"&#176;", L"°" }, { L"&#8230;", L"…" },
            { L"&#160;", L" " }, { L"&quot;", LR"(")" }, { L"&apos;", L"'" }, { L"&lt;", L"<" },
            { L"&gt;", L">" }, { L"&rsquo;", L"’" }, { L"&nbsp;", L" " }, { L"&amp;", L"&" }
        })
    {  
    }
    
    ///<summary>
    /// Accepts the Content property from a Feed and returns rich text
    /// paragraphs that can be passed to a RichTextBlock.
    ///</summary>
    String^ TextHelper::UnescapeText(String^ inStr)
    {
        wstring input(inStr->Data());
        wstring result = UnescapeText(input);
        return ref new Platform::String(result.c_str());
    }
    
    ///<summary>
    /// Create a RichText block from the text retrieved by the HtmlUtilies object. 
    /// For a more full-featured app, you could parse the content argument yourself and
    /// add the page's images to the inlines collection.
    ///</summary>
    IVector<Paragraph^>^ TextHelper::CreateRichText(String^ content,
        TypedEventHandler<Hyperlink^, HyperlinkClickEventArgs^>^ context)
    {
        std::vector<Paragraph^> blocks; 
    
        auto text = HtmlUtilities::ConvertToText(content);
        auto parts = SplitContentIntoParagraphs(wstring(text->Data()), LR"(\r\n)");
    
        // Add the link at the top. Don't set the NavigateUri property because 
        // that causes the link to open in IE even if the Click event is handled. 
        auto hlink = ref new Hyperlink();
        hlink->Click += context;
        auto linkText = ref new Run();
        linkText->Foreground = 
            ref new Windows::UI::Xaml::Media::SolidColorBrush(Windows::UI::Colors::DarkRed);
        linkText->Text = "Link";
        hlink->Inlines->Append(linkText);
        auto linkPara = ref new Paragraph();
        linkPara->Inlines->Append(hlink);
        blocks.push_back(linkPara);
    
        for (auto part : parts)
        {
            auto p = ref new Paragraph();
            p->TextIndent = 10;
            p->Margin = (10, 10, 10, 10);
            auto r = ref new Run();
            r->Text = ref new String(part.c_str());
            p->Inlines->Append(r);
            blocks.push_back(p);
        }
    
        return ref new Vector<Paragraph^>(blocks);
    }
    
    ///<summary>
    /// Split an input string which has been created by HtmlUtilities::ConvertToText
    /// into paragraphs. The rgx string we use here is LR("\r\n") . If we ever use
    /// other means to grab the raw text from a feed, then the rgx will have to recognize
    /// other possible new line formats. 
    ///</summary>
    vector<wstring> TextHelper::SplitContentIntoParagraphs(const wstring& s, const wstring& rgx)
    {    
        const wregex r(rgx);
        vector<wstring> result;
    
        // the -1 argument indicates that the text after this match until the next match
        // is the "capture group". In other words, this is how we match on what is between the tokens.
        for (wsregex_token_iterator rit(s.begin(), s.end(), r, -1), end; rit != end; ++rit)
        {
            if (rit->length() > 0)
            {
                result.push_back(*rit);
            }
        }
        return result;  
    }
    
    ///<summary>
    /// This is used to unescape html entities that occur in titles, subtitles, etc.
    //  entities is a map<wstring, wstring> with key-values like this: { L"&#60;", L"<" },
    /// CAUTION: we must not unescape any content that gets sent to the webView.
    ///</summary>
    wstring TextHelper::UnescapeText(const wstring& input)
    {
        wsmatch match;
    
        // match L"&#60;" as well as "&nbsp;"
        const wregex rgx(LR"(&#?\w*?;)");
        wstring result;
    
        // itrEnd needs to be visible outside the loop
        wsregex_iterator itrEnd, itrRemainingText;
    
        // Iterate over input and build up result as we go along
        // by first appending what comes before the match, then the 
        // unescaped replacement for the HTML entity which is the match,
        // then once at the end appending what comes after the last match.
    
        for (wsregex_iterator itr(input.cbegin(), input.cend(), rgx); itr != itrEnd; ++itr)    
        {
            wstring entity = itr->str();
            map<wstring, wstring>::const_iterator mit = entities.find(entity);
            if (mit != end(entities))
            {
                result.append(itr->prefix());
                result.append(mit->second); // mit->second is the replacement text
                itrRemainingText = itr;
            }
            else 
            {
                // we found an entity that we don't explitly map yet so just 
                // render it in raw form. Exercise for the user: add
                // all legal entities to the entities map.   
                result.append(entity);
                continue; 
            }        
        }
    
        // If we didn't find any entities to escape
        // then (a) don't try to dereference itrRemainingText
        // and (b) return input because result is empty!
        if (itrRemainingText == itrEnd)
        {
            return input;
        }
        else
        {
            // Add any text between the last match and end of input string.
            result.append(itrRemainingText->suffix());
            return result;
        }
    }
    

    Nota che la classe TextHelper personalizzata illustra alcuni dei modi in cui è possibile usare C++ ISO (std::map, std::regex, std::wstring) internamente in un'app C++/CX. Creeremo istanze di questa classe in locale nelle pagine che la usano. È sufficiente includerla una sola volta, in App.xaml.h:

    #include "TextHelper.h"
    
  3. Ora dovresti riuscire a compilare ed eseguire l'app. Non aspettarti che questa app esegua operazioni complesse.

Parte 6: avviare, sospendere e riprendere l'app

L'evento App::OnLaunched viene generato quando l'utente avvia l'app premendo o facendo clic sul riquadro dell'app e anche quando l'utente torna all'app dopo che il sistema l'ha terminata per liberare memoria per altre app. In entrambi i casi, è sempre necessario accedere a Internet e ricaricare i dati in risposta a questo evento. Tuttavia, esistono altre azioni che devono essere richiamate solo in un caso o nell'altro. È possibile dedurre questi stati osservando rootFrame in combinazione con l'argomento LaunchActivatedEventArgs passato alla funzione, per poi fare la scelta giusta. Fortunatamente, la classe SuspensionManager aggiunta automaticamente con MainPage esegue la maggior parte delle operazioni per salvare e ripristinare lo stato dell'app quando l'app viene sospesa e riavviata. Dobbiamo semplicemente chiamarne i metodi.

  1. Aggiungi i file di codice SuspensionManager alla cartella Common del progetto. Aggiungi SuspensionManager.h e copia il codice seguente:

    //
    // SuspensionManager.h
    // Declaration of the SuspensionManager class
    //
    
    #pragma once
    
    namespace SimpleBlogReader
    {
        namespace Common
        {
            /// <summary>
            /// SuspensionManager captures global session state to simplify process lifetime management
            /// for an application.  Note that session state will be automatically cleared under a variety
            /// of conditions and should only be used to store information that would be convenient to
            /// carry across sessions, but that should be disacarded when an application crashes or is
            /// upgraded.
            /// </summary>
            class SuspensionManager sealed
            {
            public:
                static void RegisterFrame(Windows::UI::Xaml::Controls::Frame^ frame, Platform::String^ sessionStateKey, Platform::String^ sessionBaseKey = nullptr);
                static void UnregisterFrame(Windows::UI::Xaml::Controls::Frame^ frame);
                static concurrency::task<void> SaveAsync();
                static concurrency::task<void> RestoreAsync(Platform::String^ sessionBaseKey = nullptr);
                static Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ SessionState();
                static Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ SessionStateForFrame(
                    Windows::UI::Xaml::Controls::Frame^ frame);
    
            private:
                static void RestoreFrameNavigationState(Windows::UI::Xaml::Controls::Frame^ frame);
                static void SaveFrameNavigationState(Windows::UI::Xaml::Controls::Frame^ frame);
    
                static Platform::Collections::Map<Platform::String^, Platform::Object^>^ _sessionState;
                static const wchar_t* sessionStateFilename;
    
                static std::vector<Platform::WeakReference> _registeredFrames;
                static Windows::UI::Xaml::DependencyProperty^ FrameSessionStateKeyProperty;
                static Windows::UI::Xaml::DependencyProperty^ FrameSessionBaseKeyProperty;
                static Windows::UI::Xaml::DependencyProperty^ FrameSessionStateProperty;
            };
        }
    }
    
  2. Aggiungi il file di codice SuspensionManager.cpp e copia il codice seguente:

    //
    // SuspensionManager.cpp
    // Implementation of the SuspensionManager class
    //
    
    #include "pch.h"
    #include "SuspensionManager.h"
    
    #include <algorithm>
    
    using namespace SimpleBlogReader::Common;
    
    using namespace concurrency;
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::Storage;
    using namespace Windows::Storage::FileProperties;
    using namespace Windows::Storage::Streams;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Interop;
    
    Map<String^, Object^>^ SuspensionManager::_sessionState = ref new Map<String^, Object^>();
    
    const wchar_t* SuspensionManager::sessionStateFilename = L"_sessionState.dat";
    
    std::vector<WeakReference> SuspensionManager::_registeredFrames;
    
    DependencyProperty^ SuspensionManager::FrameSessionStateKeyProperty =
        DependencyProperty::RegisterAttached("_FrameSessionStateKeyProperty",
        TypeName(String::typeid), TypeName(SuspensionManager::typeid), nullptr);
    
    DependencyProperty^ SuspensionManager::FrameSessionBaseKeyProperty =
        DependencyProperty::RegisterAttached("_FrameSessionBaseKeyProperty",
        TypeName(String::typeid), TypeName(SuspensionManager::typeid), nullptr);
    
    DependencyProperty^ SuspensionManager::FrameSessionStateProperty =
        DependencyProperty::RegisterAttached("_FrameSessionStateProperty",
        TypeName(IMap<String^, Object^>::typeid), TypeName(SuspensionManager::typeid), nullptr);
    
    class ObjectSerializeHelper
    {
    public:
        // Codes used for identifying serialized types
        enum StreamTypes {
            NullPtrType = 0,
    
            // Supported IPropertyValue types
            UInt8Type, UInt16Type, UInt32Type, UInt64Type, Int16Type, Int32Type, Int64Type,
            SingleType, DoubleType, BooleanType, Char16Type, GuidType, StringType,
    
            // Additional supported types
            StringToObjectMapType,
    
            // Marker values used to ensure stream integrity
            MapEndMarker
        };
        static String^ ReadString(DataReader^ reader);
        static IMap<String^, Object^>^ ReadStringToObjectMap(DataReader^ reader);
        static Object^ ReadObject(DataReader^ reader);
        static void WriteString(DataWriter^ writer, String^ string);
        static void WriteProperty(DataWriter^ writer, IPropertyValue^ propertyValue);
        static void WriteStringToObjectMap(DataWriter^ writer, IMap<String^, Object^>^ map);
        static void WriteObject(DataWriter^ writer, Object^ object);
    };
    
    /// <summary>
    /// Provides access to global session state for the current session.  This state is serialized by
    /// <see cref="SaveAsync"/> and restored by <see cref="RestoreAsync"/> which require values to be
    /// one of the following: boxed values including integers, floating-point singles and doubles,
    /// wide characters, boolean, Strings and Guids, or Map<String^, Object^> where map values are
    /// subject to the same constraints.  Session state should be as compact as possible.
    /// </summary>
    IMap<String^, Object^>^ SuspensionManager::SessionState()
    {
        return _sessionState;
    }
    
    /// <summary>
    /// Registers a <see cref="Frame"/> instance to allow its navigation history to be saved to
    /// and restored from <see cref="SessionState"/>.  Frames should be registered once
    /// immediately after creation if they will participate in session state management.  Upon
    /// registration if state has already been restored for the specified key
    /// the navigation history will immediately be restored.  Subsequent invocations of
    /// <see cref="RestoreAsync(String)"/> will also restore navigation history.
    /// </summary>
    /// <param name="frame">An instance whose navigation history should be managed by
    /// <see cref="SuspensionManager"/></param>
    /// <param name="sessionStateKey">A unique key into <see cref="SessionState"/> used to
    /// store navigation-related information.</param>
    /// <param name="sessionBaseKey">An optional key that identifies the type of session.
    /// This can be used to distinguish between multiple application launch scenarios.</param>
    void SuspensionManager::RegisterFrame(Frame^ frame, String^ sessionStateKey, String^ sessionBaseKey)
    {
        if (frame->GetValue(FrameSessionStateKeyProperty) != nullptr)
        {
            throw ref new FailureException("Frames can only be registered to one session state key");
        }
    
        if (frame->GetValue(FrameSessionStateProperty) != nullptr)
        {
            throw ref new FailureException("Frames must be either be registered before accessing frame session state, or not registered at all");
        }
    
        if (sessionBaseKey != nullptr)
        {
            frame->SetValue(FrameSessionBaseKeyProperty, sessionBaseKey);
            sessionStateKey = sessionBaseKey + "_" + sessionStateKey;
        }
    
        // Use a dependency property to associate the session key with a frame, and keep a list of frames whose
        // navigation state should be managed
        frame->SetValue(FrameSessionStateKeyProperty, sessionStateKey);
        _registeredFrames.insert(_registeredFrames.begin(), WeakReference(frame));
    
        // Check to see if navigation state can be restored
        RestoreFrameNavigationState(frame);
    }
    
    /// <summary>
    /// Disassociates a <see cref="Frame"/> previously registered by <see cref="RegisterFrame"/>
    /// from <see cref="SessionState"/>.  Any navigation state previously captured will be
    /// removed.
    /// </summary>
    /// <param name="frame">An instance whose navigation history should no longer be
    /// managed.</param>
    void SuspensionManager::UnregisterFrame(Frame^ frame)
    {
        // Remove session state and remove the frame from the list of frames whose navigation
        // state will be saved (along with any weak references that are no longer reachable)
        auto key = safe_cast<String^>(frame->GetValue(FrameSessionStateKeyProperty));
        if (SessionState()->HasKey(key))
        {
            SessionState()->Remove(key);
        }
        _registeredFrames.erase(
            std::remove_if(_registeredFrames.begin(), _registeredFrames.end(), [=](WeakReference& e)
        {
            auto testFrame = e.Resolve<Frame>();
            return testFrame == nullptr || testFrame == frame;
        }),
            _registeredFrames.end()
            );
    }
    
    /// <summary>
    /// Provides storage for session state associated with the specified <see cref="Frame"/>.
    /// Frames that have been previously registered with <see cref="RegisterFrame"/> have
    /// their session state saved and restored automatically as a part of the global
    /// <see cref="SessionState"/>.  Frames that are not registered have transient state
    /// that can still be useful when restoring pages that have been discarded from the
    /// navigation cache.
    /// </summary>
    /// <remarks>Apps may choose to rely on <see cref="NavigationHelper"/> to manage
    /// page-specific state instead of working with frame session state directly.</remarks>
    /// <param name="frame">The instance for which session state is desired.</param>
    /// <returns>A collection of state subject to the same serialization mechanism as
    /// <see cref="SessionState"/>.</returns>
    IMap<String^, Object^>^ SuspensionManager::SessionStateForFrame(Frame^ frame)
    {
        auto frameState = safe_cast<IMap<String^, Object^>^>(frame->GetValue(FrameSessionStateProperty));
    
        if (frameState == nullptr)
        {
            auto frameSessionKey = safe_cast<String^>(frame->GetValue(FrameSessionStateKeyProperty));
            if (frameSessionKey != nullptr)
            {
                // Registered frames reflect the corresponding session state
                if (!_sessionState->HasKey(frameSessionKey))
                {
                    _sessionState->Insert(frameSessionKey, ref new Map<String^, Object^>());
                }
                frameState = safe_cast<IMap<String^, Object^>^>(_sessionState->Lookup(frameSessionKey));
            }
            else
            {
                // Frames that aren't registered have transient state
                frameState = ref new Map<String^, Object^>();
            }
            frame->SetValue(FrameSessionStateProperty, frameState);
        }
        return frameState;
    }
    
    void SuspensionManager::RestoreFrameNavigationState(Frame^ frame)
    {
        auto frameState = SessionStateForFrame(frame);
        if (frameState->HasKey("Navigation"))
        {
            frame->SetNavigationState(safe_cast<String^>(frameState->Lookup("Navigation")));
        }
    }
    
    void SuspensionManager::SaveFrameNavigationState(Frame^ frame)
    {
        auto frameState = SessionStateForFrame(frame);
        frameState->Insert("Navigation", frame->GetNavigationState());
    }
    
    /// <summary>
    /// Save the current <see cref="SessionState"/>.  Any <see cref="Frame"/> instances
    /// registered with <see cref="RegisterFrame"/> will also preserve their current
    /// navigation stack, which in turn gives their active <see cref="Page"/> an opportunity
    /// to save its state.
    /// </summary>
    /// <returns>An asynchronous task that reflects when session state has been saved.</returns>
    task<void> SuspensionManager::SaveAsync(void)
    {
        // Save the navigation state for all registered frames
        for (auto && weakFrame : _registeredFrames)
        {
            auto frame = weakFrame.Resolve<Frame>();
            if (frame != nullptr) SaveFrameNavigationState(frame);
        }
    
        // Serialize the session state synchronously to avoid asynchronous access to shared
        // state
        auto sessionData = ref new InMemoryRandomAccessStream();
        auto sessionDataWriter = ref new DataWriter(sessionData->GetOutputStreamAt(0));
        ObjectSerializeHelper::WriteObject(sessionDataWriter, _sessionState);
    
        // Once session state has been captured synchronously, begin the asynchronous process
        // of writing the result to disk
        return task<unsigned int>(sessionDataWriter->StoreAsync()).then([=](unsigned int)
        {
            return ApplicationData::Current->LocalFolder->CreateFileAsync(StringReference(sessionStateFilename),
                CreationCollisionOption::ReplaceExisting);
        })
            .then([=](StorageFile^ createdFile)
        {
            return createdFile->OpenAsync(FileAccessMode::ReadWrite);
        })
            .then([=](IRandomAccessStream^ newStream)
        {
            return RandomAccessStream::CopyAsync(
                sessionData->GetInputStreamAt(0), newStream->GetOutputStreamAt(0));
        })
            .then([=](UINT64 copiedBytes)
        {
            (void) copiedBytes; // Unused parameter
            return;
        });
    }
    
    /// <summary>
    /// Restores previously saved <see cref="SessionState"/>.  Any <see cref="Frame"/> instances
    /// registered with <see cref="RegisterFrame"/> will also restore their prior navigation
    /// state, which in turn gives their active <see cref="Page"/> an opportunity restore its
    /// state.
    /// </summary>
    /// <param name="sessionBaseKey">An optional key that identifies the type of session.
    /// This can be used to distinguish between multiple application launch scenarios.</param>
    /// <returns>An asynchronous task that reflects when session state has been read.  The
    /// content of <see cref="SessionState"/> should not be relied upon until this task
    /// completes.</returns>
    task<void> SuspensionManager::RestoreAsync(String^ sessionBaseKey)
    {
        _sessionState->Clear();
    
        task<StorageFile^> getFileTask(ApplicationData::Current->LocalFolder->GetFileAsync(StringReference(sessionStateFilename)));
        return getFileTask.then([=](StorageFile^ stateFile)
        {
            task<BasicProperties^> getBasicPropertiesTask(stateFile->GetBasicPropertiesAsync());
            return getBasicPropertiesTask.then([=](BasicProperties^ stateFileProperties)
            {
                auto size = unsigned int(stateFileProperties->Size);
                if (size != stateFileProperties->Size) throw ref new FailureException("Session state larger than 4GB");
                task<IRandomAccessStreamWithContentType^> openReadTask(stateFile->OpenReadAsync());
                return openReadTask.then([=](IRandomAccessStreamWithContentType^ stateFileStream)
                {
                    auto stateReader = ref new DataReader(stateFileStream);
                    return task<unsigned int>(stateReader->LoadAsync(size)).then([=](unsigned int bytesRead)
                    {
                        (void) bytesRead; // Unused parameter
                        // Deserialize the Session State
                        Object^ content = ObjectSerializeHelper::ReadObject(stateReader);
                        _sessionState = (Map<String^, Object^>^)content;
    
                        // Restore any registered frames to their saved state
                        for (auto && weakFrame : _registeredFrames)
                        {
                            auto frame = weakFrame.Resolve<Frame>();
                            if (frame != nullptr && safe_cast<String^>(frame->GetValue(FrameSessionBaseKeyProperty)) == sessionBaseKey)
                            {
                                frame->ClearValue(FrameSessionStateProperty);
                                RestoreFrameNavigationState(frame);
                            }
                        }
                    }, task_continuation_context::use_current());
                });
            });
        });
    }
    
    #pragma region Object serialization for a known set of types
    
    void ObjectSerializeHelper::WriteString(DataWriter^ writer, String^ string)
    {
        writer->WriteByte(StringType);
        writer->WriteUInt32(writer->MeasureString(string));
        writer->WriteString(string);
    }
    
    void ObjectSerializeHelper::WriteProperty(DataWriter^ writer, IPropertyValue^ propertyValue)
    {
        switch (propertyValue->Type)
        {
        case PropertyType::UInt8:
            writer->WriteByte(StreamTypes::UInt8Type);
            writer->WriteByte(propertyValue->GetUInt8());
            return;
        case PropertyType::UInt16:
            writer->WriteByte(StreamTypes::UInt16Type);
            writer->WriteUInt16(propertyValue->GetUInt16());
            return;
        case PropertyType::UInt32:
            writer->WriteByte(StreamTypes::UInt32Type);
            writer->WriteUInt32(propertyValue->GetUInt32());
            return;
        case PropertyType::UInt64:
            writer->WriteByte(StreamTypes::UInt64Type);
            writer->WriteUInt64(propertyValue->GetUInt64());
            return;
        case PropertyType::Int16:
            writer->WriteByte(StreamTypes::Int16Type);
            writer->WriteUInt16(propertyValue->GetInt16());
            return;
        case PropertyType::Int32:
            writer->WriteByte(StreamTypes::Int32Type);
            writer->WriteUInt32(propertyValue->GetInt32());
            return;
        case PropertyType::Int64:
            writer->WriteByte(StreamTypes::Int64Type);
            writer->WriteUInt64(propertyValue->GetInt64());
            return;
        case PropertyType::Single:
            writer->WriteByte(StreamTypes::SingleType);
            writer->WriteSingle(propertyValue->GetSingle());
            return;
        case PropertyType::Double:
            writer->WriteByte(StreamTypes::DoubleType);
            writer->WriteDouble(propertyValue->GetDouble());
            return;
        case PropertyType::Boolean:
            writer->WriteByte(StreamTypes::BooleanType);
            writer->WriteBoolean(propertyValue->GetBoolean());
            return;
        case PropertyType::Char16:
            writer->WriteByte(StreamTypes::Char16Type);
            writer->WriteUInt16(propertyValue->GetChar16());
            return;
        case PropertyType::Guid:
            writer->WriteByte(StreamTypes::GuidType);
            writer->WriteGuid(propertyValue->GetGuid());
            return;
        case PropertyType::String:
            WriteString(writer, propertyValue->GetString());
            return;
        default:
            throw ref new InvalidArgumentException("Unsupported property type");
        }
    }
    
    void ObjectSerializeHelper::WriteStringToObjectMap(DataWriter^ writer, IMap<String^, Object^>^ map)
    {
        writer->WriteByte(StringToObjectMapType);
        writer->WriteUInt32(map->Size);
        for (auto && pair : map)
        {
            WriteObject(writer, pair->Key);
            WriteObject(writer, pair->Value);
        }
        writer->WriteByte(MapEndMarker);
    }
    
    void ObjectSerializeHelper::WriteObject(DataWriter^ writer, Object^ object)
    {
        if (object == nullptr)
        {
            writer->WriteByte(NullPtrType);
            return;
        }
    
        auto propertyObject = dynamic_cast<IPropertyValue^>(object);
        if (propertyObject != nullptr)
        {
            WriteProperty(writer, propertyObject);
            return;
        }
    
        auto mapObject = dynamic_cast<IMap<String^, Object^>^>(object);
        if (mapObject != nullptr)
        {
            WriteStringToObjectMap(writer, mapObject);
            return;
        }
    
        throw ref new InvalidArgumentException("Unsupported data type");
    }
    
    String^ ObjectSerializeHelper::ReadString(DataReader^ reader)
    {
        int length = reader->ReadUInt32();
        String^ string = reader->ReadString(length);
        return string;
    }
    
    IMap<String^, Object^>^ ObjectSerializeHelper::ReadStringToObjectMap(DataReader^ reader)
    {
        auto map = ref new Map<String^, Object^>();
        auto size = reader->ReadUInt32();
        for (unsigned int index = 0; index < size; index++)
        {
            auto key = safe_cast<String^>(ReadObject(reader));
            auto value = ReadObject(reader);
            map->Insert(key, value);
        }
        if (reader->ReadByte() != StreamTypes::MapEndMarker)
        {
            throw ref new InvalidArgumentException("Invalid stream");
        }
        return map;
    }
    
    Object^ ObjectSerializeHelper::ReadObject(DataReader^ reader)
    {
        auto type = reader->ReadByte();
        switch (type)
        {
        case StreamTypes::NullPtrType:
            return nullptr;
        case StreamTypes::UInt8Type:
            return reader->ReadByte();
        case StreamTypes::UInt16Type:
            return reader->ReadUInt16();
        case StreamTypes::UInt32Type:
            return reader->ReadUInt32();
        case StreamTypes::UInt64Type:
            return reader->ReadUInt64();
        case StreamTypes::Int16Type:
            return reader->ReadInt16();
        case StreamTypes::Int32Type:
            return reader->ReadInt32();
        case StreamTypes::Int64Type:
            return reader->ReadInt64();
        case StreamTypes::SingleType:
            return reader->ReadSingle();
        case StreamTypes::DoubleType:
            return reader->ReadDouble();
        case StreamTypes::BooleanType:
            return reader->ReadBoolean();
        case StreamTypes::Char16Type:
            return (char16_t) reader->ReadUInt16();
        case StreamTypes::GuidType:
            return reader->ReadGuid();
        case StreamTypes::StringType:
            return ReadString(reader);
        case StreamTypes::StringToObjectMapType:
            return ReadStringToObjectMap(reader);
        default:
            throw ref new InvalidArgumentException("Unsupported property type");
        }
    }
    
    #pragma endregion
    
  3. In app.xaml.cpp aggiungi questa direttiva include:

    #include "Common\SuspensionManager.h"
    
  4. Aggiungi la direttiva namespace seguente:

    using namespace SimpleBlogReader::Common;
    
  5. Ora sostituisci la funzione esistente con questo codice:

    void App::OnLaunched(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();
            SuspensionManager::RegisterFrame(rootFrame, "AppFrame");
    
            // Initialize the Atom and RSS feed objects with data from the web
            FeedDataSource^ feedDataSource = 
                safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
            if (feedDataSource->Feeds->Size == 0)
            {
                if (e->PreviousExecutionState == ApplicationExecutionState::Terminated)
                {
                    // On resume FeedDataSource needs to know whether the app was on a
                    // specific FeedData, which will be the unless it was on MainPage
                    // when it was terminated.
                    ApplicationDataContainer^ localSettings = ApplicationData::Current->LocalSettings;
                    auto values = localSettings->Values;
                    if (localSettings->Values->HasKey("LastViewedFeed"))
                    {
                        feedDataSource->CurrentFeedUri = 
                            safe_cast<String^>(localSettings->Values->Lookup("LastViewedFeed"));
                    }
                }
    
                feedDataSource->InitDataSource();
            }
    
            // We have 4 pages in the app
            rootFrame->CacheSize = 4;
            auto prerequisite = task<void>([](){});
            if (e->PreviousExecutionState == ApplicationExecutionState::Terminated)
            {
                // Now restore the pages if we are resuming
                prerequisite = Common::SuspensionManager::RestoreAsync();
            }
    
            // if we're starting fresh, prerequisite will execute immediately.
            // if resuming from termination, prerequisite will wait until RestoreAsync() completes.
            prerequisite.then([=]()
            {
                if (rootFrame->Content == nullptr)
                {
                    if (!rootFrame->Navigate(MainPage::typeid, e->Arguments))
                    {
                        throw ref new FailureException("Failed to create initial page");
                    }
                }
                // Place the frame in the current Window
                Window::Current->Content = rootFrame;
                Window::Current->Activate();
            }, task_continuation_context::use_current());
        }
    
        // There is a frame, but is has no content, so navigate to main page
        // and activate the window.
        else if (rootFrame->Content == nullptr)
        {
    #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
            // Removes the turnstile navigation for startup.
            if (rootFrame->ContentTransitions != nullptr)
            {
                _transitions = ref new TransitionCollection();
                for (auto transition : rootFrame->ContentTransitions)
                {
                    _transitions->Append(transition);
                }
            }
    
            rootFrame->ContentTransitions = nullptr;
            _firstNavigatedToken = rootFrame->Navigated += 
                ref new NavigatedEventHandler(this, &App::RootFrame_FirstNavigated);
    
    
    #endif
            // 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(MainPage::typeid, e->Arguments))
            {
                throw ref new FailureException("Failed to create initial page");
            }
    
            // Ensure the current window is active in this code path.
            // we also called this inside the task for the other path.
            Window::Current->Activate();
        }
    }
    

    Tieni presente che la classe App è nel progetto condiviso, pertanto il codice scritto qui verrà eseguito sia nell'app di Windows che in quella di Windows Phone, tranne quando è definita la macro WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP.

  6. Il gestore OnSuspending è più semplice. Viene chiamato quando il sistema arresta l'app, non quando l'utente la chiude. In questo caso, lasciamo che sia SuspensionManager a eseguire le operazioni necessarie. Il gestore chiamerà il gestore dell'evento SaveState in ogni pagina nell'app e serializzerà qualunque oggetto archiviato nell'oggetto PageState di ogni pagina, quindi ripristinerà i valori nelle pagine alla ripresa dell'app. Se vuoi esaminare il codice, vedi il file SuspensionManager.cpp.

    Sostituisci il corpo della funzione OnSuspending esistente con questo codice:

    void App::OnSuspending(Object^ sender, SuspendingEventArgs^ e)
    {
        (void)sender;   // Unused parameter
        (void)e;        // Unused parameter
    
        // Save application state and stop any background activity
        auto deferral = e->SuspendingOperation->GetDeferral();
        create_task(Common::SuspensionManager::SaveAsync())
            .then([deferral]()
        {
            deferral->Complete();
        });
    }
    

A questo punto potremmo avviare l'app e scaricare i dati del feed, ma non abbiamo alcuno modo per visualizzarli all'utente. È giunta l'ora di pensare all'interfaccia.

Parte 7: aggiungere la prima pagina di interfaccia utente, un elenco di feed

All'apertura dell'app vogliamo mostrare all'utente una raccolta di primo livello di tutti i feed che sono stati scaricati. In questo modo l'utente può fare clic su un elemento o toccarlo per passare a un particolare feed che contiene una raccolta di elementi del feed o post. Abbiamo già aggiunto le pagine. Nell'app di Windows si tratta di una pagina di elementi, che mostra un controllo GridView quando il dispositivo è orizzontale e un controllo ListView quando il dispositivo è verticale. Nel progetto Windows Phone non c'è una pagina di elementi, quindi abbiamo una pagina di base in cui aggiungeremo un controllo ListView manualmente. La visualizzazione elenco viene adattata automaticamente quando cambia l'orientamento del dispositivo.

In questa pagina, come in tutte, si devono in genere eseguire le stesse attività di base:

  • Aggiungere il markup XAML che descrive l'interfaccia utente ed esegue il data binding
  • Aggiungere codice personalizzato alle funzioni membro LoadState e SaveState
  • Gestire gli eventi, almeno uno dei quali include in genere il codice per lo spostamento alla pagina successiva

Eseguiremo queste operazioni in ordine, partendo dal progetto per Windows:

Aggiungere il markup XAML (MainPage)

La pagina principale esegue il rendering di ogni oggetto FeedData in un controllo GridView. Per descrivere l'aspetto dei dati, creiamo un DataTemplate, che è un albero XAML che verrà usato per il rendering di ogni elemento. Le possibilità per i DataTemplate in termini di layout, tipi di carattere, colori e così via, sono limitate solo dalla creatività e dallo stile di progettazione di ogni sviluppatore. In questa pagina, useremo un semplice modello che, dopo il rendering, avrà un aspetto simile al seguente:

Elemento del feed

  1. Uno stile XAML è come uno stile in Microsoft Word, ovvero è un modo pratico per raggruppare un insieme di valori di proprietà per un elemento XAML, il "TargetType". Uno stile può essere basato su un altro stile. L'attributo "x:Key" specifica il nome che usiamo per fare riferimento allo stile quando lo utilizziamo.

    Inserisci questo modello e gli stili di supporto nel nodo Page.Resources di MainPage.xaml (Windows 8.1). Vengono usati solo in MainPage.

    <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 Margin="0,22,16,0">
                <TextBlock Text="{Binding Title}" 
                            Style="{StaticResource GridTitleTextStyle}" 
                            Margin="10,10,10,10"/>
                <TextBlock Text="{Binding Description}" 
                            Style="{StaticResource GridDescriptionTextStyle}"
                            Margin="10,10,10,10" />
            </StackPanel>
            <Border BorderBrush="DarkRed" BorderThickness="4" VerticalAlignment="Bottom">
                <StackPanel VerticalAlignment="Bottom" Orientation="Horizontal" 
                            Background="{StaticResource GreenBlockBackgroundBrush}">
                    <TextBlock Text="Last Updated" FontWeight="Bold" Margin="12,4,0,8" 
                                Height="42"/>
                    <TextBlock Text="{Binding PubDate, Converter={StaticResource dateConverter}}" 
                                FontWeight="ExtraBold" Margin="4,4,12,8" Height="42" Width="88"/>
                </StackPanel>
            </Border>
        </Grid>
    </DataTemplate>
    

    Verrà visualizzata una linea rossa ondulata sotto GreenBlockBackgroundBrush e ce ne occuperemo fra poco.

  2. Ancora in MainPage.xaml (Windows 8.1), elimina l'elemento AppName locale per la pagina in modo che non nasconda l'elemento globale che aggiungeremo con ambito App.

  3. Aggiungi un oggetto CollectionViewSource al nodo Page.Resources. Questo oggetto connette il controllo ListView al modello di dati:

    <!-- Collection of items displayed by this page -->
            <CollectionViewSource
            x:Name="itemsViewSource"
            Source="{Binding Items}"/>
    

    Tieni presente che l'elemento Page ha già un attributo DataContext impostato sulla proprietà DefaultViewModel per la classe MainPage. Impostiamo tale proprietà come FeedDataSource e pertanto CollectionViewSource cerca qui una raccolta Items e la trova.

  4. In App.xaml aggiungiamo una stringa di risorsa globale per il nome dell'app, con alcune altre risorse a cui verrà fatto riferimento da più pagine nell'app. Aggiungendo qui le risorse, non dobbiamo definirle separatamente in ogni pagina. Aggiungi questi elementi al nodo Resources in App.xaml:

            <x:String x:Key="AppName">Simple Blog Reader</x:String>        
    
            <SolidColorBrush x:Key="WindowsBlogBackgroundBrush" Color="#FF0A2562"/>
            <SolidColorBrush x:Key="GreenBlockBackgroundBrush" Color="#FF6BBD46"/>
            <Style x:Key="WindowsBlogLayoutRootStyle" TargetType="Panel">
                <Setter Property="Background" 
                        Value="{StaticResource WindowsBlogBackgroundBrush}"/>
            </Style>
    
            <!-- Green square in all ListViews that displays the date -->
            <ControlTemplate x:Key="DateBlockTemplate">
                <Viewbox Stretch="Fill">
                    <Canvas Height="86" Width="86"  Margin="4,0,4,4" 
                     HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                        <TextBlock TextTrimming="WordEllipsis" 
                                   Padding="0,0,0,0"
                                   TextWrapping="NoWrap" 
                                   Width="Auto"
                                   Height="Auto" 
                                   FontSize="32" 
                                   FontWeight="Bold">
                            <TextBlock.Text>
                                <Binding Path="PubDate" 
                                         Converter="{StaticResource dateConverter}"
                                         ConverterParameter="month"/>
                            </TextBlock.Text>
                        </TextBlock>
    
                        <TextBlock TextTrimming="WordEllipsis" 
                                   TextWrapping="Wrap" 
                                   Width="Auto" 
                                   Height="Auto" 
                                   FontSize="32" 
                                   FontWeight="Bold" 
                                   Canvas.Top="36">
                            <TextBlock.Text>
                                <Binding Path="PubDate"  
                                         Converter="{StaticResource dateConverter}"
                                         ConverterParameter="day"/>
                            </TextBlock.Text>
                        </TextBlock>
    
                        <Line Stroke="White" 
                              StrokeThickness="2" X1="50" Y1="46" X2="50" Y2="80" />
    
                        <TextBlock TextWrapping="Wrap"  
                                   Height="Auto"  
                                   FontSize="18" 
                                   FontWeight="Bold"
                             FontStretch="Condensed"
                                   LineHeight="18"
                                   LineStackingStrategy="BaselineToBaseline"
                                   Canvas.Top="38" 
                                   Canvas.Left="56">
                            <TextBlock.Text>
                                <Binding Path="PubDate" 
                                         Converter="{StaticResource dateConverter}"
                                         ConverterParameter="year"  />
                            </TextBlock.Text>
                        </TextBlock>
                    </Canvas>
                </Viewbox>
            </ControlTemplate>
    
            <!-- Describes the layout for items in all ListViews -->
            <DataTemplate x:Name="ListItemTemplate">
                <Grid Margin="5,0,0,0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="72"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition MaxHeight="54"></RowDefinition>
                    </Grid.RowDefinitions>
                    <!-- Green date block -->
                    <Border Background="{StaticResource GreenBlockBackgroundBrush}"
                            VerticalAlignment="Top">
                        <ContentControl Template="{StaticResource DateBlockTemplate}" />
                    </Border>
                    <TextBlock Grid.Column="1"
                               Text="{Binding Title}"
                               Margin="10,0,0,0" FontSize="20" 
                               TextWrapping="Wrap"
                               MaxHeight="72" 
                               Foreground="#FFFE5815" />
                </Grid>
            </DataTemplate>
    

In MainPage viene visualizzato un elenco di feed. Quando il dispositivo è usato con orientamento orizzontale useremo un controllo GridView, che supporta lo scorrimento orizzontale. Con l'orientamento verticale useremo un controllo ListView, che supporta lo scorrimento verticale. Vogliamo che l'utente sia in grado di usare l'app in entrambi gli orientamenti. È relativamente semplice implementare il supporto delle modifiche di orientamento:

  • Aggiungi entrambi i controlli nella pagina e imposta ItemSource sullo stesso valore di collectionViewSource. Imposta la proprietà Visibility in ListView su Collapsed in modo che per impostazione predefinita non sia visibile.
  • Crea un set di due oggetti VisualState, uno che descrive il comportamento dell'interfaccia utente per l'orientamento orizzontale e uno che descrive il comportamento per l'orientamento verticale.
  • Gestisci l'evento Window::SizeChanged, generato quando cambia l'orientamento o quando l'utente restringe o allarga la finestra. Controlla l'altezza e la larghezza delle nuove dimensioni. Se l'altezza è maggiore della larghezza, richiama VisualState per l'orientamento verticale. In caso contrario richiama lo stato per l'orientamento orizzontale.

Hh465045.wedge(it-it,WIN.10).gifAggiungere i controlli GridView e ListView

  1. Nel file MainPage.xaml aggiungi questi controlli GridView e ListView e la griglia che contiene il titolo della pagina e il pulsante Indietro:

     <Grid Style="{StaticResource WindowsBlogLayoutRootStyle}">
            <Grid.ChildrenTransitions>
                <TransitionCollection>
                    <EntranceThemeTransition/>
                </TransitionCollection>
            </Grid.ChildrenTransitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="140"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
    
            <!-- 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"  Margin="0,-10,0,10">
            </GridView>
    
            <!-- Vertical scrolling list -->
            <ListView
                x:Name="ItemListView"
                Visibility="Collapsed"            
                AutomationProperties.AutomationId="ItemsListView"
                AutomationProperties.Name="Items"
                TabIndex="1" Grid.Row="1" Margin="-10,-10,0,0"      
                IsItemClickEnabled="True"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                IsSwipeEnabled="False"            
                ItemClick="ItemGridView_ItemClick"
                ItemTemplate="{StaticResource ListItemTemplate}">
    
                <ListView.ItemContainerStyle>
                    <Style TargetType="FrameworkElement">
                        <Setter Property="Margin" Value="2,0,0,2"/>
                    </Style>
                </ListView.ItemContainerStyle>
            </ListView>
    
            <!-- Back button and page title -->
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="120"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Button x:Name="backButton" Margin="39,59,39,0" 
                        Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
                        Style="{StaticResource NavigationBackButtonNormalStyle}"
                        VerticalAlignment="Top"
                        AutomationProperties.Name="Back"
                        AutomationProperties.AutomationId="BackButton"
                        AutomationProperties.ItemType="Navigation Button"/>
                <TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" 
                        Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1" 
                        IsHitTestVisible="false" TextWrapping="NoWrap" 
                        VerticalAlignment="Bottom" Margin="0,0,30,40"/>
            </Grid>
    
  2. Nota che entrambi i controlli usano la stessa funzione membro per l'evento ItemClick. Posiziona il punto di inserimento in uno di questi e premi F12 per generare automaticamente lo stub del gestore eventi. Aggiungeremo il codice in un secondo momento.

  3. Incolla la definizione VisualStateGroups in modo che sia l'ultimo elemento all'interno della griglia radice (non inserirla all'esterno della griglia perché in questo caso non funzionerebbe). Nota che sono presenti due stati, ma solo uno è definito in modo esplicito. Il motivo è che lo stato DefaultLayout è già descritto nel codice XAML per questa pagina.

    <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="ViewStates">
            <VisualState x:Name="DefaultLayout"/>
            <VisualState x:Name="Portrait">
                <Storyboard>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" 
                           Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView" 
                           Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                </ObjectAnimationUsingKeyFrames>
               </Storyboard>
            </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
    
  4. La definizione dell'interfaccia utente è ora completa. Dobbiamo semplicemente indicare alla pagina cosa fare al caricamento.

LoadState e SaveState (MainPage dell'app di Windows)

Le due funzioni membro principali a cui dobbiamo prestare attenzione per qualsiasi pagina XAML sono LoadState e (a volte) SaveState. In LoadState popoliamo i dati per la pagina e in SaveState salviamo tutti i dati che saranno necessari per ripopolare la pagina, in caso di sospensione e successivo riavvio.

  • Sostituisci l'implementazione di LoadState con questo codice, che consente di inserire i dati del feed caricati (o ancora in fase di caricamento) dall'origine feedDataSource creata all'avvio e di inserire i dati nel nostro ViewModel per questa pagina.

    void MainPage::LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e)
    {     
        auto feedDataSource = safe_cast<FeedDataSource^>  
        (App::Current->Resources->Lookup("feedDataSource"));
        this->DefaultViewModel->Insert("Items", feedDataSource->Feeds);
    }
    

    Non è necessario chiamare SaveState per MainPage perché non c'è niente da ricordare per questa pagina, dato che visualizza sempre tutti i feed.

Gestori eventi (MainPage dell'app di Windows)

Tutte le pagine risiedono concettualmente in un elemento Frame ed è questo elemento che usiamo per spostarci avanti e indietro tra le pagine. Il secondo parametro in una chiamata della funzione Navigate viene usato per passare i dati da una pagina all'altra. Qualsiasi oggetto passato viene archiviato e serializzato automaticamente da SuspensionManager ogni volta che l'app viene sospesa, in modo che i valori possano essere ripristinati alla ripresa dell'app. Il SuspensionManager predefinito supporta solo i tipi predefiniti String e Guid. Se ti serve una serializzazione più elaborata, puoi creare un SuspensionManager personalizzato. In questo caso passiamo un valore String, che verrà usato da SplitPage per cercare il feed corrente.

Hh465045.wedge(it-it,WIN.10).gifPer attivare gli spostamenti in seguito ai clic sugli elementi

  1. Quando l'utente fa clic su un elemento nella griglia, il gestore eventi recupera l'elemento su cui è stato fatto clic, lo imposta come "feed corrente" nel caso l'app venga sospesa in seguito, quindi passa alla pagina successiva. Passa il titolo del feed alla pagina successiva in modo che la pagina possa cercare i dati di tale feed. Ecco il codice da incollare:

    void MainPage::ItemGridView_ItemClick(Object^ sender, ItemClickEventArgs^ e)
    {
        // We must manually cast from Object^ to FeedData^.
        auto feedData = safe_cast<FeedData^>(e->ClickedItem);
    
        // Store the feed and tell other pages it's loaded and ready to go.
        auto app = safe_cast<App^>(App::Current);
        app->SetCurrentFeed(feedData);
    
        // Only navigate if there are items in the feed
        if (feedData->Items->Size > 0)
        {
            // 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(SplitPage::typeid, feedData->Title);
        }
    }
    
  2. Per fare in modo che il codice precedente possa essere compilato, dobbiamo usare la direttiva #include per SplitPage.xaml.h all'inizio del file corrente, MainPage.xaml.cpp (Windows 8.1):

    #include "SplitPage.xaml.h"
    

Hh465045.wedge(it-it,WIN.10).gifPer gestire l'evento Page_SizeChanged

  • In MainPage.xaml aggiungi un nome all'elemento radice aggiungendo x:Name="pageRoot" agli attributi dell'elemento della pagina radice e quindi aggiungi un attributo SizeChanged="pageRoot_SizeChanged" per creare un gestore di eventi. Sostituisci l'implementazione del gestore nel file CPP con questo codice:

    void MainPage::pageRoot_SizeChanged(Platform::Object^ sender, SizeChangedEventArgs^ e)
    {
        if (e->NewSize.Height / e->NewSize.Width >= 1)
        {
            VisualStateManager::GoToState(this, "Portrait", false);
        }
        else
        {
            VisualStateManager::GoToState(this, "DefaultLayout", false);
        }
    } 
    

    Aggiungi quindi la dichiarazione di questa funzione alla classe MainPage in MainPage.xaml.h.

    private:
        void pageRoot_SizeChanged(Platform::Object^ sender, SizeChangedEventArgs^ e);
    

    Il codice è di immediata comprensione. Se ora esegui l'app nel simulatore e ruoti il dispositivo, vedrai il cambiamento nell'interfaccia utente per i controlli GridView e ListView.

Aggiungere codice XAML (MainPage dell'app di Windows Phone)

Dedichiamoci ora a far funzionare la pagina principale dell'app di Windows Phone. In questo caso servirà molto meno codice perché useremo tutto il codice che abbiamo inserito nel progetto condiviso. Le app di Windows Phone, inoltre, non supportano controlli GridView perché le schermate sono troppo piccole per un funzionamento corretto. Useremo quindi un controllo ListView che verrà adattato automaticamente all'orientamento orizzontale e non saranno necessarie modifiche di VisualState. Inizieremo aggiungendo l'attributo DataContext all'elemento Page. Questo non viene generato automaticamente in una pagina di base per il telefono, come nel caso di una pagina ItemsPage o SplitPage.

  1. Per implementare lo spostamento di pagina, le pagine richiedono NavigationHelper, che a sua volta dipende da RelayCommand. Aggiungi un nuovo elemento, RelayCommand.h, e copia questo codice:

    //
    // RelayCommand.h
    // Declaration of the RelayCommand and associated classes
    //
    
    #pragma once
    
    // <summary>
    // A command whose sole purpose is to relay its functionality 
    // to other objects by invoking delegates. 
    // The default return value for the CanExecute method is 'true'.
    // <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
    // <see cref="CanExecute"/> is expected to return a different value.
    // </summary>
    
    
    namespace SimpleBlogReader
    {
        namespace Common
        {
            [Windows::Foundation::Metadata::WebHostHidden]
            public ref class RelayCommand sealed :[Windows::Foundation::Metadata::Default] Windows::UI::Xaml::Input::ICommand
            {
            public:
                virtual event Windows::Foundation::EventHandler<Object^>^ CanExecuteChanged;
                virtual bool CanExecute(Object^ parameter);
                virtual void Execute(Object^ parameter);
                virtual ~RelayCommand();
    
            internal:
                RelayCommand(std::function<bool(Platform::Object^)> canExecuteCallback,
                    std::function<void(Platform::Object^)> executeCallback);
                void RaiseCanExecuteChanged();
    
            private:
                std::function<bool(Platform::Object^)> _canExecuteCallback;
                std::function<void(Platform::Object^)> _executeCallback;
            };
        }
    }
    
  2. Nella cartella Common aggiungi RelayCommand.cpp e copia questo codice:

    //
    // RelayCommand.cpp
    // Implementation of the RelayCommand and associated classes
    //
    
    #include "pch.h"
    #include "RelayCommand.h"
    #include "NavigationHelper.h"
    
    using namespace SimpleBlogReader::Common;
    
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::System;
    using namespace Windows::UI::Core;
    using namespace Windows::UI::ViewManagement;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Navigation;
    
    /// <summary>
    /// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
    /// </summary>
    /// <param name="parameter">
    /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
    /// </param>
    /// <returns>true if this command can be executed; otherwise, false.</returns>
    bool RelayCommand::CanExecute(Object^ parameter)
    {
        return (_canExecuteCallback) (parameter);
    }
    
    /// <summary>
    /// Executes the <see cref="RelayCommand"/> on the current command target.
    /// </summary>
    /// <param name="parameter">
    /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
    /// </param>
    void RelayCommand::Execute(Object^ parameter)
    {
        (_executeCallback) (parameter);
    }
    
    /// <summary>
    /// Method used to raise the <see cref="CanExecuteChanged"/> event
    /// to indicate that the return value of the <see cref="CanExecute"/>
    /// method has changed.
    /// </summary>
    void RelayCommand::RaiseCanExecuteChanged()
    {
        CanExecuteChanged(this, nullptr);
    }
    
    /// <summary>
    /// RelayCommand Class Destructor.
    /// </summary>
    RelayCommand::~RelayCommand()
    {
        _canExecuteCallback = nullptr;
        _executeCallback = nullptr;
    };
    
    /// <summary>
    /// Creates a new command that can always execute.
    /// </summary>
    /// <param name="canExecuteCallback">The execution status logic.</param>
    /// <param name="executeCallback">The execution logic.</param>
    RelayCommand::RelayCommand(std::function<bool(Platform::Object^)> canExecuteCallback,
        std::function<void(Platform::Object^)> executeCallback) :
        _canExecuteCallback(canExecuteCallback),
        _executeCallback(executeCallback)
        {
        }
    
  3. Nella cartella Common aggiungi NavigationHelper.h e copia questo codice:

    //
    // NavigationHelper.h
    // Declaration of the NavigationHelper and associated classes
    //
    
    #pragma once
    
    #include "RelayCommand.h"
    
    namespace SimpleBlogReader
    {
        namespace Common
        {
            /// <summary>
            /// Class used to hold the event data required when a page attempts to load state.
            /// </summary>
            public ref class LoadStateEventArgs sealed
            {
            public:
    
                /// <summary>
                /// The parameter value passed to <see cref="Frame->Navigate(Type, Object)"/> 
                /// when this page was initially requested.
                /// </summary>
                property Platform::Object^ NavigationParameter
                {
                    Platform::Object^ get();
                }
    
                /// <summary>
                /// A dictionary of state preserved by this page during an earlier
                /// session.  This will be null the first time a page is visited.
                /// </summary>
                property Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ PageState
                {
                    Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ get();
                }
    
            internal:
                LoadStateEventArgs(Platform::Object^ navigationParameter, Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ pageState);
    
            private:
                Platform::Object^ _navigationParameter;
                Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ _pageState;
            };
    
            /// <summary>
            /// Represents the method that will handle the <see cref="NavigationHelper->LoadState"/>event
            /// </summary>
            public delegate void LoadStateEventHandler(Platform::Object^ sender, LoadStateEventArgs^ e);
    
            /// <summary>
            /// Class used to hold the event data required when a page attempts to save state.
            /// </summary>
            public ref class SaveStateEventArgs sealed
            {
            public:
    
                /// <summary>
                /// An empty dictionary to be populated with serializable state.
                /// </summary>
                property Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ PageState
                {
                    Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ get();
                }
    
            internal:
                SaveStateEventArgs(Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ pageState);
    
            private:
                Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ _pageState;
            };
    
            /// <summary>
            /// Represents the method that will handle the <see cref="NavigationHelper->SaveState"/>event
            /// </summary>
            public delegate void SaveStateEventHandler(Platform::Object^ sender, SaveStateEventArgs^ e);
    
            /// <summary>
            /// NavigationHelper aids in navigation between pages.  It provides commands used to 
            /// navigate back and forward as well as registers for standard mouse and keyboard 
            /// shortcuts used to go back and forward in Windows and the hardware back button in
            /// Windows Phone.  In addition it integrates SuspensionManger to handle process lifetime
            /// management and state management when navigating between pages.
            /// </summary>
            /// <example>
            /// To make use of NavigationHelper, follow these two steps or
            /// start with a BasicPage or any other Page item template other than BlankPage.
            /// 
            /// 1) Create an instance of the NavigationHelper somewhere such as in the 
            ///     constructor for the page and register a callback for the LoadState and 
            ///     SaveState events.
            /// <code>
            ///     MyPage::MyPage()
            ///     {
            ///         InitializeComponent();
            ///         auto navigationHelper = ref new Common::NavigationHelper(this);
            ///         navigationHelper->LoadState += ref new Common::LoadStateEventHandler(this, &MyPage::LoadState);
            ///         navigationHelper->SaveState += ref new Common::SaveStateEventHandler(this, &MyPage::SaveState);
            ///     }
            ///     
            ///     void MyPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
            ///     { }
            ///     void MyPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
            ///     { }
            /// </code>
            /// 
            /// 2) Register the page to call into the NavigationHelper whenever the page participates 
            ///     in navigation by overriding the <see cref="Windows::UI::Xaml::Controls::Page::OnNavigatedTo"/> 
            ///     and <see cref="Windows::UI::Xaml::Controls::Page::OnNavigatedFrom"/> events.
            /// <code>
            ///     void MyPage::OnNavigatedTo(NavigationEventArgs^ e)
            ///     {
            ///         NavigationHelper->OnNavigatedTo(e);
            ///     }
            ///
            ///     void MyPage::OnNavigatedFrom(NavigationEventArgs^ e)
            ///     {
            ///         NavigationHelper->OnNavigatedFrom(e);
            ///     }
            /// </code>
            /// </example>
            [Windows::Foundation::Metadata::WebHostHidden]
            [Windows::UI::Xaml::Data::Bindable]
            public ref class NavigationHelper sealed
            {
            public:
                /// <summary>
                /// <see cref="RelayCommand"/> used to bind to the back Button's Command property
                /// for navigating to the most recent item in back navigation history, if a Frame
                /// manages its own navigation history.
                /// 
                /// The <see cref="RelayCommand"/> is set up to use the virtual method <see cref="GoBack"/>
                /// as the Execute Action and <see cref="CanGoBack"/> for CanExecute.
                /// </summary>
                property RelayCommand^ GoBackCommand
                {
                    RelayCommand^ get();
                }
    
                /// <summary>
                /// <see cref="RelayCommand"/> used for navigating to the most recent item in 
                /// the forward navigation history, if a Frame manages its own navigation history.
                /// 
                /// The <see cref="RelayCommand"/> is set up to use the virtual method <see cref="GoForward"/>
                /// as the Execute Action and <see cref="CanGoForward"/> for CanExecute.
                /// </summary>
                property RelayCommand^ GoForwardCommand
                {
                    RelayCommand^ get();
                }
    
            internal:
                NavigationHelper(Windows::UI::Xaml::Controls::Page^ page,
                    RelayCommand^ goBack = nullptr,
                    RelayCommand^ goForward = nullptr);
    
                bool CanGoBack();
                void GoBack();
                bool CanGoForward();
                void GoForward();
    
                void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e);
                void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e);
    
                event LoadStateEventHandler^ LoadState;
                event SaveStateEventHandler^ SaveState;
    
            private:
                Platform::WeakReference _page;
    
                RelayCommand^ _goBackCommand;
                RelayCommand^ _goForwardCommand;
    
    #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
                Windows::Foundation::EventRegistrationToken _backPressedEventToken;
    
                void HardwareButton_BackPressed(Platform::Object^ sender,
                    Windows::Phone::UI::Input::BackPressedEventArgs^ e);
    #else
                bool _navigationShortcutsRegistered;
                Windows::Foundation::EventRegistrationToken _acceleratorKeyEventToken;
                Windows::Foundation::EventRegistrationToken _pointerPressedEventToken;
    
                void CoreDispatcher_AcceleratorKeyActivated(Windows::UI::Core::CoreDispatcher^ sender,
                    Windows::UI::Core::AcceleratorKeyEventArgs^ e);
                void CoreWindow_PointerPressed(Windows::UI::Core::CoreWindow^ sender,
                    Windows::UI::Core::PointerEventArgs^ e);
    #endif
    
                Platform::String^ _pageKey;
                Windows::Foundation::EventRegistrationToken _loadedEventToken;
                Windows::Foundation::EventRegistrationToken _unloadedEventToken;
                void OnLoaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
                void OnUnloaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
    
                ~NavigationHelper();
            };
        }
    }
    
  4. Aggiungi ora il file di implementazione, NavigationHelper.cpp, nella stessa cartella con il codice seguente:

    //
    // NavigationHelper.cpp
    // Implementation of the NavigationHelper and associated classes
    //
    
    #include "pch.h"
    #include "NavigationHelper.h"
    #include "RelayCommand.h"
    #include "SuspensionManager.h"
    
    using namespace SimpleBlogReader::Common;
    
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::System;
    using namespace Windows::UI::Core;
    using namespace Windows::UI::ViewManagement;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Interop;
    using namespace Windows::UI::Xaml::Navigation;
    
    #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
    using namespace Windows::Phone::UI::Input;
    #endif
    
    /// <summary>
    /// Initializes a new instance of the <see cref="LoadStateEventArgs"/> class.
    /// </summary>
    /// <param name="navigationParameter">
    /// The parameter value passed to <see cref="Frame->Navigate(Type, Object)"/> 
    /// when this page was initially requested.
    /// </param>
    /// <param name="pageState">
    /// A dictionary of state preserved by this page during an earlier
    /// session.  This will be null the first time a page is visited.
    /// </param>
    LoadStateEventArgs::LoadStateEventArgs(Object^ navigationParameter, IMap<String^, Object^>^ pageState)
    {
        _navigationParameter = navigationParameter;
        _pageState = pageState;
    }
    
    /// <summary>
    /// Gets the <see cref="NavigationParameter"/> property of <see cref"LoadStateEventArgs"/> class.
    /// </summary>
    Object^ LoadStateEventArgs::NavigationParameter::get()
    {
        return _navigationParameter;
    }
    
    /// <summary>
    /// Gets the <see cref="PageState"/> property of <see cref"LoadStateEventArgs"/> class.
    /// </summary>
    IMap<String^, Object^>^ LoadStateEventArgs::PageState::get()
    {
        return _pageState;
    }
    
    /// <summary>
    /// Initializes a new instance of the <see cref="SaveStateEventArgs"/> class.
    /// </summary>
    /// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
    SaveStateEventArgs::SaveStateEventArgs(IMap<String^, Object^>^ pageState)
    {
        _pageState = pageState;
    }
    
    /// <summary>
    /// Gets the <see cref="PageState"/> property of <see cref"SaveStateEventArgs"/> class.
    /// </summary>
    IMap<String^, Object^>^ SaveStateEventArgs::PageState::get()
    {
        return _pageState;
    }
    
    /// <summary>
    /// Initializes a new instance of the <see cref="NavigationHelper"/> class.
    /// </summary>
    /// <param name="page">A reference to the current page used for navigation.  
    /// This reference allows for frame manipulation and to ensure that keyboard 
    /// navigation requests only occur when the page is occupying the entire window.</param>
    NavigationHelper::NavigationHelper(Page^ page, RelayCommand^ goBack, RelayCommand^ goForward) :
    _page(page),
    _goBackCommand(goBack),
    _goForwardCommand(goForward)
    {
        // When this page is part of the visual tree make two changes:
        // 1) Map application view state to visual state for the page
        // 2) Handle hardware navigation requests
        _loadedEventToken = page->Loaded += ref new RoutedEventHandler(this, &NavigationHelper::OnLoaded);
    
        //// Undo the same changes when the page is no longer visible
        _unloadedEventToken = page->Unloaded += ref new RoutedEventHandler(this, &NavigationHelper::OnUnloaded);
    }
    
    NavigationHelper::~NavigationHelper()
    {
        delete _goBackCommand;
        delete _goForwardCommand;
    
        _page = nullptr;
    }
    
    /// <summary>
    /// Invoked when the page is part of the visual tree
    /// </summary>
    /// <param name="sender">Instance that triggered the event.</param>
    /// <param name="e">Event data describing the conditions that led to the event.</param>
    void NavigationHelper::OnLoaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
    #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
        _backPressedEventToken = HardwareButtons::BackPressed +=
            ref new EventHandler<BackPressedEventArgs^>(this,
            &NavigationHelper::HardwareButton_BackPressed);
    #else
        Page ^page = _page.Resolve<Page>();
    
        // Keyboard and mouse navigation only apply when occupying the entire window
        if (page != nullptr &&
            page->ActualHeight == Window::Current->Bounds.Height &&
            page->ActualWidth == Window::Current->Bounds.Width)
        {
            // Listen to the window directly so focus isn't required
            _acceleratorKeyEventToken = Window::Current->CoreWindow->Dispatcher->AcceleratorKeyActivated +=
                ref new TypedEventHandler<CoreDispatcher^, AcceleratorKeyEventArgs^>(this,
                &NavigationHelper::CoreDispatcher_AcceleratorKeyActivated);
    
            _pointerPressedEventToken = Window::Current->CoreWindow->PointerPressed +=
                ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this,
                &NavigationHelper::CoreWindow_PointerPressed);
    
            _navigationShortcutsRegistered = true;
        }
    #endif
    }
    
    /// <summary>
    /// Invoked when the page is removed from visual tree
    /// </summary>
    /// <param name="sender">Instance that triggered the event.</param>
    /// <param name="e">Event data describing the conditions that led to the event.</param>
    void NavigationHelper::OnUnloaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
    #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
        HardwareButtons::BackPressed -= _backPressedEventToken;
    #else
        if (_navigationShortcutsRegistered)
        {
            Window::Current->CoreWindow->Dispatcher->AcceleratorKeyActivated -= _acceleratorKeyEventToken;
            Window::Current->CoreWindow->PointerPressed -= _pointerPressedEventToken;
            _navigationShortcutsRegistered = false;
        }
    #endif
    
        // Remove handler and release the reference to page
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            page->Loaded -= _loadedEventToken;
            page->Unloaded -= _unloadedEventToken;
            delete _goBackCommand;
            delete _goForwardCommand;
            _goForwardCommand = nullptr;
            _goBackCommand = nullptr;
        }
    }
    
    #pragma region Navigation support
    
    /// <summary>
    /// Method used by the <see cref="GoBackCommand"/> property
    /// to determine if the <see cref="Frame"/> can go back.
    /// </summary>
    /// <returns>
    /// true if the <see cref="Frame"/> has at least one entry 
    /// in the back navigation history.
    /// </returns>
    bool NavigationHelper::CanGoBack()
    {
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            auto frame = page->Frame;
            return (frame != nullptr && frame->CanGoBack);
        }
    
        return false;
    }
    
    /// <summary>
    /// Method used by the <see cref="GoBackCommand"/> property
    /// to invoke the <see cref="Windows::UI::Xaml::Controls::Frame::GoBack"/> method.
    /// </summary>
    void NavigationHelper::GoBack()
    {
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            auto frame = page->Frame;
            if (frame != nullptr && frame->CanGoBack)
            {
                frame->GoBack();
            }
        }
    }
    
    /// <summary>
    /// Method used by the <see cref="GoForwardCommand"/> property
    /// to determine if the <see cref="Frame"/> can go forward.
    /// </summary>
    /// <returns>
    /// true if the <see cref="Frame"/> has at least one entry 
    /// in the forward navigation history.
    /// </returns>
    bool NavigationHelper::CanGoForward()
    {
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            auto frame = page->Frame;
            return (frame != nullptr && frame->CanGoForward);
        }
    
        return false;
    }
    
    /// <summary>
    /// Method used by the <see cref="GoForwardCommand"/> property
    /// to invoke the <see cref="Windows::UI::Xaml::Controls::Frame::GoBack"/> method.
    /// </summary>
    void NavigationHelper::GoForward()
    {
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            auto frame = page->Frame;
            if (frame != nullptr && frame->CanGoForward)
            {
                frame->GoForward();
            }
        }
    }
    
    /// <summary>
    /// Gets the <see cref="GoBackCommand"/> property of <see cref"NavigationHelper"/> class.
    /// </summary>
    RelayCommand^ NavigationHelper::GoBackCommand::get()
    {
        if (_goBackCommand == nullptr)
        {
            _goBackCommand = ref new RelayCommand(
                [this](Object^) -> bool
            {
                return CanGoBack();
            },
                [this](Object^) -> void
            {
                GoBack();
            }
            );
        }
        return _goBackCommand;
    }
    
    /// <summary>
    /// Gets the <see cref="GoForwardCommand"/> property of <see cref"NavigationHelper"/> class.
    /// </summary>
    RelayCommand^ NavigationHelper::GoForwardCommand::get()
    {
        if (_goForwardCommand == nullptr)
        {
            _goForwardCommand = ref new RelayCommand(
                [this](Object^) -> bool
            {
                return CanGoForward();
            },
                [this](Object^) -> void
            {
                GoForward();
            }
            );
        }
        return _goForwardCommand;
    }
    
    #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
    /// <summary>
    /// Handles the back button press and navigates through the history of the root frame.
    /// </summary>
    void NavigationHelper::HardwareButton_BackPressed(Object^ sender, BackPressedEventArgs^ e)
    {
        if (this->GoBackCommand->CanExecute(nullptr))
        {
            e->Handled = true;
            this->GoBackCommand->Execute(nullptr);
        }
    }
    #else
    /// <summary>
    /// Invoked on every keystroke, including system keys such as Alt key combinations, when
    /// this page is active and occupies the entire window.  Used to detect keyboard navigation
    /// between pages even when the page itself doesn't have focus.
    /// </summary>
    /// <param name="sender">Instance that triggered the event.</param>
    /// <param name="e">Event data describing the conditions that led to the event.</param>
    void NavigationHelper::CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher^ sender,
        AcceleratorKeyEventArgs^ e)
    {
        sender; // Unused parameter
        auto virtualKey = e->VirtualKey;
    
        // Only investigate further when Left, Right, or the dedicated Previous or Next keys
        // are pressed
        if ((e->EventType == CoreAcceleratorKeyEventType::SystemKeyDown ||
            e->EventType == CoreAcceleratorKeyEventType::KeyDown) &&
            (virtualKey == VirtualKey::Left || virtualKey == VirtualKey::Right ||
            virtualKey == VirtualKey::GoBack || virtualKey == VirtualKey::GoForward))
        {
            auto coreWindow = Window::Current->CoreWindow;
            auto downState = Windows::UI::Core::CoreVirtualKeyStates::Down;
            bool menuKey = (coreWindow->GetKeyState(VirtualKey::Menu) & downState) == downState;
            bool controlKey = (coreWindow->GetKeyState(VirtualKey::Control) & downState) == downState;
            bool shiftKey = (coreWindow->GetKeyState(VirtualKey::Shift) & downState) == downState;
            bool noModifiers = !menuKey && !controlKey && !shiftKey;
            bool onlyAlt = menuKey && !controlKey && !shiftKey;
    
            if ((virtualKey == VirtualKey::GoBack && noModifiers) ||
                (virtualKey == VirtualKey::Left && onlyAlt))
            {
                // When the previous key or Alt+Left are pressed navigate back
                e->Handled = true;
                GoBackCommand->Execute(this);
            }
            else if ((virtualKey == VirtualKey::GoForward && noModifiers) ||
                (virtualKey == VirtualKey::Right && onlyAlt))
            {
                // When the next key or Alt+Right are pressed navigate forward
                e->Handled = true;
                GoForwardCommand->Execute(this);
            }
        }
    }
    
    /// <summary>
    /// Invoked on every mouse click, touch screen tap, or equivalent interaction when this
    /// page is active and occupies the entire window.  Used to detect browser-style next and
    /// previous mouse button clicks to navigate between pages.
    /// </summary>
    /// <param name="sender">Instance that triggered the event.</param>
    /// <param name="e">Event data describing the conditions that led to the event.</param>
    void NavigationHelper::CoreWindow_PointerPressed(CoreWindow^ sender, PointerEventArgs^ e)
    {
        auto properties = e->CurrentPoint->Properties;
    
        // Ignore button chords with the left, right, and middle buttons
        if (properties->IsLeftButtonPressed ||
            properties->IsRightButtonPressed ||
            properties->IsMiddleButtonPressed)
        {
            return;
        }
    
        // If back or foward are pressed (but not both) navigate appropriately
        bool backPressed = properties->IsXButton1Pressed;
        bool forwardPressed = properties->IsXButton2Pressed;
        if (backPressed ^ forwardPressed)
        {
            e->Handled = true;
            if (backPressed)
            {
                if (GoBackCommand->CanExecute(this))
                {
                    GoBackCommand->Execute(this);
                }
            }
            else
            {
                if (GoForwardCommand->CanExecute(this))
                {
                    GoForwardCommand->Execute(this);
                }
            }
        }
    }
    #endif
    
    #pragma endregion
    
    #pragma region Process lifetime management
    
    /// <summary>
    /// Invoked when this page is about to be displayed in a Frame.
    /// </summary>
    /// <param name="e">Event data that describes how this page was reached.  The Parameter
    /// property provides the group to be displayed.</param>
    void NavigationHelper::OnNavigatedTo(NavigationEventArgs^ e)
    {
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            auto frameState = SuspensionManager::SessionStateForFrame(page->Frame);
            _pageKey = "Page-" + page->Frame->BackStackDepth;
    
            if (e->NavigationMode == NavigationMode::New)
            {
                // Clear existing state for forward navigation when adding a new page to the
                // navigation stack
                auto nextPageKey = _pageKey;
                int nextPageIndex = page->Frame->BackStackDepth;
                while (frameState->HasKey(nextPageKey))
                {
                    frameState->Remove(nextPageKey);
                    nextPageIndex++;
                    nextPageKey = "Page-" + nextPageIndex;
                }
    
                // Pass the navigation parameter to the new page
                LoadState(this, ref new LoadStateEventArgs(e->Parameter, nullptr));
            }
            else
            {
                // Pass the navigation parameter and preserved page state to the page, using
                // the same strategy for loading suspended state and recreating pages discarded
                // from cache
                LoadState(this, ref new LoadStateEventArgs(e->Parameter, safe_cast<IMap<String^, Object^>^>(frameState->Lookup(_pageKey))));
            }
        }
    }
    
    /// <summary>
    /// Invoked when this page will no longer be displayed in a Frame.
    /// </summary>
    /// <param name="e">Event data that describes how this page was reached.  The Parameter
    /// property provides the group to be displayed.</param>
    void NavigationHelper::OnNavigatedFrom(NavigationEventArgs^ e)
    {
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            auto frameState = SuspensionManager::SessionStateForFrame(page->Frame);
            auto pageState = ref new Map<String^, Object^>();
            SaveState(this, ref new SaveStateEventArgs(pageState));
            frameState->Insert(_pageKey, pageState);
        }
    }
    #pragma endregion
    
  5. Ora puoi aggiungere codice per includere NavigationHelper nel file di intestazione MainPage.xaml.h, nonché la proprietà DefaultViewModel che ci servirà più avanti.

    //
    // MainPage.xaml.h
    // Declaration of the MainPage class
    //
    
    #pragma once
    
    #include "MainPage.g.h"
    #include "Common\NavigationHelper.h"
    
    namespace SimpleBlogReader
    {
    
        namespace WFC = Windows::Foundation::Collections;
        namespace WUIX = Windows::UI::Xaml;
        namespace WUIXNav = Windows::UI::Xaml::Navigation;
        namespace WUIXControls = Windows::UI::Xaml::Controls;
    
        /// <summary>
        /// A basic page that provides characteristics common to most applications.
        /// </summary>
        [Windows::Foundation::Metadata::WebHostHidden]
        public ref class MainPage sealed
        {
        public:
            MainPage();
    
            /// <summary>
            /// Gets the view model for this <see cref="Page"/>. 
            /// This can be changed to a strongly typed view model.
            /// </summary>
            property WFC::IObservableMap<Platform::String^, Platform::Object^>^ DefaultViewModel
            {
                WFC::IObservableMap<Platform::String^, Platform::Object^>^  get();
            }
    
            /// <summary>
            /// Gets the <see cref="NavigationHelper"/> associated with this <see cref="Page"/>.
            /// </summary>
            property Common::NavigationHelper^ NavigationHelper
            {
                Common::NavigationHelper^ get();
            }
    
        protected:
            virtual void OnNavigatedTo(WUIXNav::NavigationEventArgs^ e) override;
            virtual void OnNavigatedFrom(WUIXNav::NavigationEventArgs^ e) override;
    
        private:
            void LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e);
            void SaveState(Platform::Object^ sender, Common::SaveStateEventArgs^ e);
    
            static WUIX::DependencyProperty^ _defaultViewModelProperty;
            static WUIX::DependencyProperty^ _navigationHelperProperty;
    
        };
    
    }
    
  6. In MainPage.xaml.cpp aggiungi l'implementazione di NavigationHelper e gli stub per il caricamento e il salvataggio dello stato, nonché le proprietà DefaultViewModel. Aggiungerai anche le direttive namespace richieste in modo che il codice finale abbia questo aspetto:

    //
    // MainPage.xaml.cpp
    // Implementation of the MainPage class
    //
    
    #include "pch.h"
    #include "MainPage.xaml.h"
    
    using namespace SimpleBlogReader;
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Controls::Primitives;
    using namespace Windows::UI::Xaml::Data;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Media;
    using namespace Windows::UI::Xaml::Navigation;
    using namespace Windows::UI::Xaml::Interop;
    
    // The Basic Page item template is documented at https://go.microsoft.com/fwlink/?LinkID=390556
    
    MainPage::MainPage()
    {
        InitializeComponent();
        SetValue(_defaultViewModelProperty, 
            ref new Platform::Collections::Map<String^, Object^>(std::less<String^>()));
        auto navigationHelper = ref new Common::NavigationHelper(this);
        SetValue(_navigationHelperProperty, navigationHelper);
        navigationHelper->LoadState += 
            ref new Common::LoadStateEventHandler(this, &MainPage::LoadState);
        navigationHelper->SaveState += 
            ref new Common::SaveStateEventHandler(this, &MainPage::SaveState);
    }
    
    DependencyProperty^ MainPage::_defaultViewModelProperty =
    DependencyProperty::Register("DefaultViewModel",
    TypeName(IObservableMap<String^, Object^>::typeid), TypeName(MainPage::typeid), nullptr);
    
    /// <summary>
    /// Used as a trivial view model.
    /// </summary>
    IObservableMap<String^, Object^>^ MainPage::DefaultViewModel::get()
    {
        return safe_cast<IObservableMap<String^, Object^>^>(GetValue(_defaultViewModelProperty));
    }
    
    DependencyProperty^ MainPage::_navigationHelperProperty =
    DependencyProperty::Register("NavigationHelper",
    TypeName(Common::NavigationHelper::typeid), TypeName(MainPage::typeid), nullptr);
    
    /// <summary>
    /// Gets an implementation of <see cref="NavigationHelper"/> designed to be
    /// used as a trivial view model.
    /// </summary>
    Common::NavigationHelper^ MainPage::NavigationHelper::get()
    {
        return safe_cast<Common::NavigationHelper^>(GetValue(_navigationHelperProperty));
    }
    
    #pragma region Navigation support
    
    /// The methods provided in this section are simply used to allow
    /// NavigationHelper to respond to the page's navigation methods.
    /// 
    /// Page specific logic should be placed in event handlers for the  
    /// <see cref="NavigationHelper::LoadState"/>
    /// and <see cref="NavigationHelper::SaveState"/>.
    /// The navigation parameter is available in the LoadState method 
    /// in addition to page state preserved during an earlier session.
    
    void MainPage::OnNavigatedTo(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedTo(e);
    }
    
    void MainPage::OnNavigatedFrom(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedFrom(e);
    }
    
    #pragma endregion
    
    /// <summary>
    /// Populates the page with content passed during navigation. Any saved state is also
    /// provided when recreating a page from a prior session.
    /// </summary>
    /// <param name="sender">
    /// The source of the event; typically <see cref="NavigationHelper"/>
    /// </param>
    /// <param name="e">Event data that provides both the navigation parameter passed to
    /// <see cref="Frame::Navigate(Type, Object)"/> when this page was initially requested and
    /// a dictionary of state preserved by this page during an earlier
    /// session. The state will be null the first time a page is visited.</param>
    void MainPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
     (void) sender; // Unused parameter
        (void) e; // Unused parameter
    }
    
    /// <summary>
    /// Preserves state associated with this page in case the application is suspended or the
    /// page is discarded from the navigation cache.  Values must conform to the serialization
    /// requirements of <see cref="SuspensionManager::SessionState"/>.
    /// </summary>
    /// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
    /// <param name="e">Event data that provides an empty dictionary to be populated with
    /// serializable state.</param>
    void MainPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
        (void) sender;  // Unused parameter
        (void) e; // Unused parameter
    }
    
  7. Sempre in MainPage.xaml (Windows Phone 8.1), spostandoti verso il basso nella pagina, trova il commento "Title Panel" e rimuovi l'intero StackPanel. Nel telefono abbiamo bisogno di un'area dello schermo per elencare i feed di blog.

  8. Più in basso nella pagina troverai un controllo Grid con questo commento: "TODO: Content should be placed within the following grid". Inserisci questo controllo ListView all'interno di tale Grid:

        <!-- Vertical scrolling item list -->
         <ListView
            x:Name="itemListView"           
            AutomationProperties.AutomationId="itemListView"
            AutomationProperties.Name="Items"
            TabIndex="1" 
            IsItemClickEnabled="True"
            ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
            IsSwipeEnabled="False"
            ItemClick="ItemListView_ItemClick"
            SelectionMode="Single"
            ItemTemplate="{StaticResource ListItemTemplate}">
    
            <ListView.ItemContainerStyle>
                <Style TargetType="FrameworkElement">
                    <Setter Property="Margin" Value="2,0,0,2"/>
                </Style>
            </ListView.ItemContainerStyle>
        </ListView>
    
  9. Ora posiziona il cursore sull'evento ItemListView_ItemClick e premi F12 (Vai a definizione). Visual Studio genera automaticamente una funzione del gestore eventi vuota. Vi aggiungeremo codice in un secondo momento. Per il momento è sufficiente generare la funzione in modo che l'app venga compilata.

Parte 8: elencare i post e visualizzare il testo di un post selezionato

In questa sezione aggiungeremo due pagine all'app di Windows Phone: la pagina in cui sono elencati i post e la pagina che mostra la versione testuale di un post selezionato. Nell'app di Windows è sufficiente aggiungere una singola pagina chiamata SplitPage nella quale l'elenco viene visualizzato su un lato e il testo del post selezionato sull'altro. Iniziamo con le pagine per il telefono.

Aggiungere il markup XAML (FeedPage dell'app di Windows Phone)

Rimaniamo nel progetto per Windows Phone e interveniamo sulla pagina FeedPage, che elenca i post per il feed selezionato dall'utente.

Hh465045.wedge(it-it,WIN.10).gif

  1. In FeedPage.xaml (Windows Phone 8.1) aggiungi un contesto dati per l'elemento Page:

    DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
    
  2. Aggiungi ora CollectionViewSource dopo l'elemento Page di apertura:

    <Page.Resources>
        <!-- Collection of items displayed by this page -->
        <CollectionViewSource
        x:Name="itemsViewSource"
        Source="{Binding Items}"/>
    </Page.Resources>
    
  3. Nell'elemento Grid aggiungi questo StackPanel:

            <!-- TitlePanel -->
            <StackPanel Grid.Row="0" Margin="24,17,0,28">
                <TextBlock Text="{StaticResource AppName}" 
                           Style="{ThemeResource TitleTextBlockStyle}" 
                           Typography.Capitals="SmallCaps"/>
            </StackPanel>
    
  4. Aggiungi quindi il controllo ListView all'interno della griglia (subito dopo l'elemento di apertura):

                <!-- Vertical scrolling item list -->
                <ListView
                x:Name="itemListView"
                AutomationProperties.AutomationId="ItemsListView"
                AutomationProperties.Name="Items"
                TabIndex="1"
                Grid.Row="1"
                Margin="-10,-10,0,0" 
                IsItemClickEnabled="True"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                IsSwipeEnabled="False"
                ItemClick="ItemListView_ItemClick"
                ItemTemplate="{StaticResource ListItemTemplate}">
    
                    <ListView.ItemContainerStyle>
                        <Style TargetType="FrameworkElement">
                            <Setter Property="Margin" Value="2,0,0,2"/>
                        </Style>
                    </ListView.ItemContainerStyle>
                </ListView>
    

    Nota che la proprietà ItemsSource del controllo ListView esegue il binding a CollectionViewSource, che esegue il binding alla nostra proprietà FeedData::Items, che inseriamo nella proprietà DefaultViewModel in LoadState nel code-behind (vedi di seguito).

  5. C'è un evento ItemClick dichiarato nel controllo ListView. Posiziona il cursore su di esso e premi F12 per generare il gestore eventi nel code-behind. Lo lasceremo vuoto per il momento.

LoadState e SaveState (FeedPage dell'app di Windows Phone)

In MainPage non abbiamo dovuto preoccuparci di archiviare lo stato, perché per la pagina è prevista sempre la reinizializzazione completa da Internet a ogni avvio dell'app per qualsiasi motivo. Le altre pagine devono ricordare lo stato. Ad esempio, se l'app viene terminata (scaricata dalla memoria) mentre FeedPage è visualizzata, quando l'utente torna a questa pagina, vogliamo che sembri che l'app non sia mai stata interrotta. Dobbiamo quindi tenere traccia del feed selezionato. La posizione in cui archiviare questi dati è lo spazio di archiviazione AppData locale e il momento ideale per archiviare è quando l'utente fa clic su questi dati in MainPage.

Però c'è una complicazione: i dati esistono già effettivamente? Se ci stiamo spostando su FeedPage da MainPage in seguito al clic dell'utente, siamo sicuri che l'oggetto selezionato FeedData esista già, perché non comparirebbe nell'elenco MainPage in caso contrario. In caso di ripresa dell'app, invece, è possibile che l'ultimo oggetto FeedData visualizzato non sia ancora stato caricato quando FeedPage tenta di eseguire il binding. Quindi, FeedPage (e altre pagine) devono avere un modo per sapere quando FeedData è disponibile. L'evento concurrency::task_completion_event è progettato proprio per una situazione di questo tipo. Possiamo usarlo per recuperare l'oggetto FeedData in modo sicuro nello stesso percorso del codice, indipendentemente dal fatto che l'app venga ripresa oppure che si tratti di uno spostamento completamente nuovo da MainPage. Da FeedPage recuperiamo sempre il feed chiamando GetCurrentFeedAsync. Nel caso di uno spostamento da MainPage l'evento è già stato impostato quando l'utente ha fatto clic su un feed e quindi il metodo restituisce immediatamente il feed. Per la ripresa da una sospensione, l'evento viene impostato nella funzione FeedDataSource::InitDataSource e in questo caso FeedPage potrebbe dover attendere un po' che il feed venga ricaricato. In questo caso, l'attesa è meglio che un arresto anomalo. Questa piccola complicazione è il motivo della presenza di molto del codice asincrono complesso in FeedData.cpp e App.xaml.cpp. Se esamini questo codice più da vicino, però, scoprirai che non è così complicato come potrebbe inizialmente sembrare.

  1. In FeedPage.xaml.cpp aggiungi questo spazio dei nomi per riportare gli oggetti attività nell'ambito:

    using namespace concurrency;
    
  2. E aggiungi una direttiva #include per TextViewerPage.xaml.h:

    #include "TextViewerPage.xaml.h"
    

    La definizione della classe TextViewerPage è necessaria nella chiamata a Navigate, illustrata di seguito.

  3. Sostituisci il metodo LoadState con il codice seguente:

    void FeedPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
    
        if (!this->DefaultViewModel->HasKey("Feed"))
        {
            auto app = safe_cast<App^>(App::Current);
            app->GetCurrentFeedAsync().then([this, e](FeedData^ fd)
            {
                // Insert into the ViewModel for this page to
                // initialize itemsViewSource->View
                this->DefaultViewModel->Insert("Feed", fd);
                this->DefaultViewModel->Insert("Items", fd->Items);
            }, task_continuation_context::use_current());
        }
    }
    

    Se torniamo a FeedPage da una pagina più in alto nello stack delle pagine, allora la pagina sarà già inizializzata (vale a dire DefaultViewModel avrà un valore per "Feed") e il feed corrente sarà già impostato correttamente. Ma se ci spostiamo in avanti da MainPage o in caso di ripresa dell'app, dovremo recuperare il feed corrente per popolare la pagina con i dati corretti. GetCurrentFeedAsync attenderà se necessario l'arrivo dei dati del feed dopo la ripresa. Specifichiamo il contesto use_current() per indicare all'attività di tornare al thread dell'interfaccia utente prima di tentare l'accesso alla proprietà di dipendenza DefaultViewModel. Gli oggetti correlati a XAML in genere non sono accessibili direttamente da thread in background.

    Non dobbiamo intervenire su SaveState in questa pagina perché otteniamo lo stato dal metodo GetCurrentFeedAsync ogni volta che viene caricata la pagina.

  4. Aggiungi la dichiarazione di LoadState nel file di intestazione FeedPage.xaml.h, aggiungi una direttiva include per "Common\NavigationHelper.h" e aggiungi le proprietà NavigationHelper e DefaultViewModel.

    //
    // FeedPage.xaml.h
    // Declaration of the FeedPage class
    //
    
    #pragma once
    
    #include "FeedPage.g.h"
    #include "Common\NavigationHelper.h"
    
    namespace SimpleBlogReader
    {
    
        namespace WFC = Windows::Foundation::Collections;
        namespace WUIX = Windows::UI::Xaml;
        namespace WUIXNav = Windows::UI::Xaml::Navigation;
        namespace WUIXControls = Windows::UI::Xaml::Controls;
    
                    /// <summary>
                    /// A basic page that provides characteristics common to most applications.
                    /// </summary>
                    [Windows::Foundation::Metadata::WebHostHidden]
                    public ref class FeedPage sealed
                    {
                    public:
                                    FeedPage();
    
                                    /// <summary>
                                    /// Gets the view model for this <see cref="Page"/>. 
                                    /// This can be changed to a strongly typed view model.
                                    /// </summary>
                                    property WFC::IObservableMap<Platform::String^, Platform::Object^>^ DefaultViewModel
                                    {
                                                    WFC::IObservableMap<Platform::String^, Platform::Object^>^  get();
                                    }
    
                                    /// <summary>
                                    /// Gets the <see cref="NavigationHelper"/> associated with this <see cref="Page"/>.
                                    /// </summary>
                                    property Common::NavigationHelper^ NavigationHelper
                                    {
                                                    Common::NavigationHelper^ get();
                                    }
    
                    protected:
                                    virtual void OnNavigatedTo(WUIXNav::NavigationEventArgs^ e) override;
                                    virtual void OnNavigatedFrom(WUIXNav::NavigationEventArgs^ e) override;
    
                    private:
                                    void LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e);
                                    void SaveState(Platform::Object^ sender, Common::SaveStateEventArgs^ e);
    
                                    static Windows::UI::Xaml::DependencyProperty^ _defaultViewModelProperty;
                                    static Windows::UI::Xaml::DependencyProperty^ _navigationHelperProperty;
            void ItemListView_ItemClick(Platform::Object^ sender, WUIXControls::ItemClickEventArgs^ e);
        };
    
    }
    
  5. Aggiungi l'implementazione di queste proprietà in FeedPage.xaml.cpp, che ora questo aspetto:

    //
    // FeedPage.xaml.cpp
    // Implementation of the FeedPage class
    //
    
    #include "pch.h"
    #include "FeedPage.xaml.h"
    #include "TextViewerPage.xaml.h"
    
    using namespace SimpleBlogReader;
    using namespace concurrency;
    
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::Graphics::Display;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Controls::Primitives;
    using namespace Windows::UI::Xaml::Data;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Interop;
    using namespace Windows::UI::Xaml::Media;
    using namespace Windows::UI::Xaml::Navigation;
    
    // The Basic Page item template is documented at https://go.microsoft.com/fwlink/?LinkID=390556
    
    FeedPage::FeedPage()
    {
        InitializeComponent();
        SetValue(_defaultViewModelProperty, 
            ref new Platform::Collections::Map<String^, Object^>(std::less<String^>()));
        auto navigationHelper = ref new Common::NavigationHelper(this);
        SetValue(_navigationHelperProperty, navigationHelper);
        navigationHelper->LoadState += 
            ref new Common::LoadStateEventHandler(this, &FeedPage::LoadState);
        navigationHelper->SaveState += 
            ref new Common::SaveStateEventHandler(this, &FeedPage::SaveState);
    }
    
    DependencyProperty^ FeedPage::_defaultViewModelProperty =
    DependencyProperty::Register("DefaultViewModel",
    TypeName(IObservableMap<String^, Object^>::typeid), TypeName(FeedPage::typeid), nullptr);
    
    /// <summary>
    /// Used as a trivial view model.
    /// </summary>
    IObservableMap<String^, Object^>^ FeedPage::DefaultViewModel::get()
    {
        return safe_cast<IObservableMap<String^, Object^>^>(GetValue(_defaultViewModelProperty));
    }
    
    DependencyProperty^ FeedPage::_navigationHelperProperty =
    DependencyProperty::Register("NavigationHelper",
    TypeName(Common::NavigationHelper::typeid), TypeName(FeedPage::typeid), nullptr);
    
    /// <summary>
    /// Gets an implementation of <see cref="NavigationHelper"/> designed to be
    /// used as a trivial view model.
    /// </summary>
    Common::NavigationHelper^ FeedPage::NavigationHelper::get()
    {
        return safe_cast<Common::NavigationHelper^>(GetValue(_navigationHelperProperty));
    }
    
    #pragma region Navigation support
    
    /// The methods provided in this section are simply used to allow
    /// NavigationHelper to respond to the page's navigation methods.
    /// 
    /// Page specific logic should be placed in event handlers for the  
    /// <see cref="NavigationHelper::LoadState"/>
    /// and <see cref="NavigationHelper::SaveState"/>.
    /// The navigation parameter is available in the LoadState method 
    /// in addition to page state preserved during an earlier session.
    
    void FeedPage::OnNavigatedTo(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedTo(e);
    }
    
    void FeedPage::OnNavigatedFrom(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedFrom(e);
    }
    
    #pragma endregion
    
    /// <summary>
    /// Populates the page with content passed during navigation. Any saved state is also
    /// provided when recreating a page from a prior session.
    /// </summary>
    /// <param name="sender">
    /// The source of the event; typically <see cref="NavigationHelper"/>
    /// </param>
    /// <param name="e">Event data that provides both the navigation parameter passed to
    /// <see cref="Frame::Navigate(Type, Object)"/> when this page was initially requested and
    /// a dictionary of state preserved by this page during an earlier
    /// session. The state will be null the first time a page is visited.</param>
    void FeedPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
    
        (void)sender; // Unused parameter
    
        if (!this->DefaultViewModel->HasKey("Feed"))
        {
            auto app = safe_cast<App^>(App::Current);
            app->GetCurrentFeedAsync().then([this, e](FeedData^ fd)
            {
                // Insert into the ViewModel for this page to 
                // initialize itemsViewSource->View
                this->DefaultViewModel->Insert("Feed", fd);
                this->DefaultViewModel->Insert("Items", fd->Items);
            }, task_continuation_context::use_current());
        }
    }
    
    /// <summary>
    /// Preserves state associated with this page in case the application is suspended or the
    /// page is discarded from the navigation cache.  Values must conform to the serialization
    /// requirements of <see cref="SuspensionManager::SessionState"/>.
    /// </summary>
    /// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
    /// <param name="e">Event data that provides an empty dictionary to be populated with
    /// serializable state.</param>
    void FeedPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
        (void)sender; // Unused parameter
    }
    
    

Gestori eventi (FeedPage dell'app di Windows Phone)

In FeedPage gestiamo un evento, ossia l'evento ItemClick che attiva lo spostamento in avanti verso la pagina in cui l'utente può leggere il post. Hai già creato un gestore stub premendo F12 sul nome dell'evento nel codice XAML.

  1. Sostituiamo ora l'implementazione con questo codice.

    void FeedPage::ItemListView_ItemClick(Platform::Object^ sender, ItemClickEventArgs^ e)
    {
        FeedItem^ clickedItem = dynamic_cast<FeedItem^>(e->ClickedItem);
        this->Frame->Navigate(TextViewerPage::typeid, clickedItem->Link->AbsoluteUri);
    }
    
  2. Premi F5 per compilare ed eseguire l'app di Windows Phone nell'emulatore. A questo punto, quando selezioni un elemento da MainPage, l'app dovrebbe passare a FeedPage e visualizzare un elenco di feed. Il passaggio successivo consiste nel visualizzare il testo per un feed selezionato.

Hh465045.wedge(it-it,WIN.10).gifAggiungere il markup XAML (TextViewerPage dell'app di Windows Phone)

  1. Nel progetto per Windows Phone, in TextViewerPage.xaml, sostituisci il pannello del titolo e la griglia del contenuto con questo markup che visualizzerà il nome dell'app (in modo discreto) e il titolo del post corrente insieme a un rendering di testo semplice del contenuto:

     <!-- TitlePanel -->
            <StackPanel Grid.Row="0" Margin="24,17,0,28">
                <TextBlock Text="{StaticResource AppName}" 
                           Style="{ThemeResource TitleTextBlockStyle}" 
                           Typography.Capitals="SmallCaps"/>
                <TextBlock x:Name="FeedItemTitle" Margin="0,12,0,0" 
                           Style="{StaticResource SubheaderTextBlockStyle}" 
                           TextWrapping="Wrap"/>
            </StackPanel>
    
            <!--TODO: Content should be placed within the following grid-->
            <Grid Grid.Row="1" x:Name="ContentRoot">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>
    
                <ScrollViewer
                x:Name="itemDetail"
                AutomationProperties.AutomationId="ItemDetailScrollViewer"
                Grid.Row="1"
                Padding="20,20,20,20"
                HorizontalScrollBarVisibility="Disabled" 
                    VerticalScrollBarVisibility="Auto"
                ScrollViewer.HorizontalScrollMode="Disabled" 
                    ScrollViewer.VerticalScrollMode="Enabled"
                ScrollViewer.ZoomMode="Disabled" Margin="4,0,-4,0">
                    <!--Border enables background color for rich text block-->
                    <Border x:Name="contentViewBorder" BorderBrush="#FFFE5815"  
                            Background="AntiqueWhite" BorderThickness="6" Grid.Row="1">
                        <RichTextBlock x:Name="BlogTextBlock" Foreground="Black" 
                                       FontFamily="Segoe WP" FontSize="24" 
                                       Padding="10,10,10,10" 
                                       VerticalAlignment="Bottom" >
                        </RichTextBlock>
                    </Border>
                </ScrollViewer>
            </Grid>
    
  2. In TextViewerPage.xaml.h Aggiungi le proprietà NavigationHelper e DefaultViewItems e quindi aggiungi anche un membro privato m_FeedItem per archiviare un riferimento all'elemento del feed corrente dopo averlo cercato per la prima volta tramite la funzione GetFeedItem che abbiamo aggiunto alla classe App nel passaggio precedente.

    Aggiungi anche una funzione RichTextHyperlinkClicked. TextViewerPage.xaml.h ora dovrebbe avere l'aspetto seguente:

    //
    // TextViewerPage.xaml.h
    // Declaration of the TextViewerPage class
    //
    
    #pragma once
    
    #include "TextViewerPage.g.h"
    #include "Common\NavigationHelper.h"
    
    namespace SimpleBlogReader
    {
        namespace WFC = Windows::Foundation::Collections;
        namespace WUIX = Windows::UI::Xaml;
        namespace WUIXNav = Windows::UI::Xaml::Navigation;
        namespace WUIXDoc = Windows::UI::Xaml::Documents;
        namespace WUIXControls = Windows::UI::Xaml::Controls;
    
                    /// <summary>
                    /// A basic page that provides characteristics common to most applications.
                    /// </summary>
                    [Windows::Foundation::Metadata::WebHostHidden]
                    public ref class TextViewerPage sealed
                    {
                    public:
                                    TextViewerPage();
    
                                    /// <summary>
                                    /// Gets the view model for this <see cref="Page"/>. 
                                    /// This can be changed to a strongly typed view model.
                                    /// </summary>
                                    property WFC::IObservableMap<Platform::String^, Platform::Object^>^ DefaultViewModel
                                    {
                                                    WFC::IObservableMap<Platform::String^, Platform::Object^>^  get();
                                    }
    
                                    /// <summary>
                                    /// Gets the <see cref="NavigationHelper"/> associated with this <see cref="Page"/>.
                                    /// </summary>
                                    property Common::NavigationHelper^ NavigationHelper
                                    {
                                                    Common::NavigationHelper^ get();
                                    }
    
    
    
                    protected:
                                    virtual void OnNavigatedTo(WUIXNav::NavigationEventArgs^ e) override;
                                    virtual void OnNavigatedFrom(WUIXNav::NavigationEventArgs^ e) override;
                                    void RichTextHyperlinkClicked(WUIXDoc::Hyperlink^ link, 
                                         WUIXDoc::HyperlinkClickEventArgs^ args);
    
                    private:
                                    void LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e);
                                    void SaveState(Platform::Object^ sender, Common::SaveStateEventArgs^ e);
    
                                    static Windows::UI::Xaml::DependencyProperty^ _defaultViewModelProperty;
                                    static Windows::UI::Xaml::DependencyProperty^ _navigationHelperProperty;
    
            FeedItem^ m_feedItem;
        };
    
    }
    

Hh465045.wedge(it-it,WIN.10).gifLoadState e SaveState (TextViewerPage dell'app di Windows Phone)

  1. In TextViewerPage.xaml.cpp aggiungi questa direttiva include:

    #include "WebViewerPage.xaml.h"
    
  2. Aggiungi queste due direttive namespace:

    using namespace concurrency;
    using namespace Windows::UI::Xaml::Documents;
    
  3. Aggiungi il codice per NavigationHelper e DefaultViewModel.

    TextViewerPage::TextViewerPage()
    {
        InitializeComponent();
        SetValue(_defaultViewModelProperty, 
            ref new Platform::Collections::Map<String^, Object^>(std::less<String^>()));
        auto navigationHelper = ref new Common::NavigationHelper(this);
        SetValue(_navigationHelperProperty, navigationHelper);
        navigationHelper->LoadState += 
            ref new Common::LoadStateEventHandler(this, &TextViewerPage::LoadState);
        navigationHelper->SaveState += 
            ref new Common::SaveStateEventHandler(this, &TextViewerPage::SaveState);
    
      //  this->DataContext = DefaultViewModel;
    
    }
    
    DependencyProperty^ TextViewerPage::_defaultViewModelProperty =
    DependencyProperty::Register("DefaultViewModel",
    TypeName(IObservableMap<String^, Object^>::typeid), TypeName(TextViewerPage::typeid), nullptr);
    
    /// <summary>
    /// Used as a trivial view model.
    /// </summary>
    IObservableMap<String^, Object^>^ TextViewerPage::DefaultViewModel::get()
    {
        return safe_cast<IObservableMap<String^, Object^>^>(GetValue(_defaultViewModelProperty));
    }
    
    DependencyProperty^ TextViewerPage::_navigationHelperProperty =
    DependencyProperty::Register("NavigationHelper",
    TypeName(Common::NavigationHelper::typeid), TypeName(TextViewerPage::typeid), nullptr);
    
    /// <summary>
    /// Gets an implementation of <see cref="NavigationHelper"/> designed to be
    /// used as a trivial view model.
    /// </summary>
    Common::NavigationHelper^ TextViewerPage::NavigationHelper::get()
    {
        return safe_cast<Common::NavigationHelper^>(GetValue(_navigationHelperProperty));
    }
    
    #pragma region Navigation support
    
    /// The methods provided in this section are simply used to allow
    /// NavigationHelper to respond to the page's navigation methods.
    /// 
    /// Page specific logic should be placed in event handlers for the  
    /// <see cref="NavigationHelper::LoadState"/>
    /// and <see cref="NavigationHelper::SaveState"/>.
    /// The navigation parameter is available in the LoadState method 
    /// in addition to page state preserved during an earlier session.
    
    void TextViewerPage::OnNavigatedTo(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedTo(e);
    }
    
    void TextViewerPage::OnNavigatedFrom(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedFrom(e);
    }
    
    #pragma endregion
    
  4. Sostituisci ora le implementazioni di LoadState e SaveState con questo codice:

    void TextViewerPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
        // (void)e; // Unused parameter
    
        auto app = safe_cast<App^>(App::Current);
        app->GetCurrentFeedAsync().then([this, app, e](FeedData^ fd)
        {        
            m_feedItem = app->GetFeedItem(fd, safe_cast<String^>(e->NavigationParameter));
            FeedItemTitle->Text = m_feedItem->Title;
            BlogTextBlock->Blocks->Clear();
            TextHelper^ helper = ref new TextHelper();
    
            auto blocks = helper->
                CreateRichText(m_feedItem->Content, 
                    ref new TypedEventHandler<Hyperlink^, HyperlinkClickEventArgs^>
                    (this, &TextViewerPage::RichTextHyperlinkClicked));
            for (auto b : blocks)
            {
                BlogTextBlock->Blocks->Append(b);
            }
        }, task_continuation_context::use_current());    
    }
    
    void TextViewerPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
    
        e->PageState->Insert("Uri", m_feedItem->Link->AbsoluteUri);
    }
    

    Non possiamo eseguire un binding a RichTextBlock, quindi ne costruiamo il contenuto manualmente tramite la classe TextHelper. Per semplicità usiamo la funzione HtmlUtilities::ConvertToText, che estrae solo il testo dal feed. Per esercitarti, puoi provare ad analizzare da solo il codice HTML o XML e poi ad aggiungere i collegamenti alle immagini e il testo alla raccolta Blocks. SyndicationClient include una funzione per l'analisi dei feed XML. Alcuni feed sono in formato XML ben formato e altri no.

Hh465045.wedge(it-it,WIN.10).gifGestori eventi (TextViewerPage dell'app di Windows Phone)

  1. In TextViewerPage passiamo a WebViewerPage tramite un Hyperlink nel testo RTF. Questo in genere non è il modo per spostarsi tra le pagine, ma ci sembra appropriato in questo caso e ci consente di esplorare il funzionamento dei collegamenti ipertestuali. Abbiamo già aggiunto la firma della funzione in TextViewerPage.xaml.h. Aggiungi ora l'implementazione in TextViewerPage.xaml.cpp:

    ///<summary>
    /// Invoked when the user clicks on the "Link" text at the top of the rich text 
    /// view of the feed. This navigates to the web page. Identical action to using
    /// the App bar "forward" button.
    ///</summary>
    void TextViewerPage::RichTextHyperlinkClicked(Hyperlink^ hyperLink, 
        HyperlinkClickEventArgs^ args)
    {
        this->Frame->Navigate(WebViewerPage::typeid, m_feedItem->Link->AbsoluteUri);
    }
    
  2. Imposta il progetto Windows Phone come progetto di avvio e premi F5. Dovresti essere in grado di fare clic su un elemento nella pagina dei feed e passare a TextViewerPage dove puoi leggere il post di blog. Questi blog tra l'altro offrono contenuti interessanti.

Aggiungere il codice XAML (SplitPage dell'app di Windows)

L'app di Windows si comporta in modo diverso dall'app di Windows Phone per alcuni aspetti. Abbiamo già visto come MainPage.xaml nel progetto Windows usi un modello ItemsPage, non disponibile nelle app di Windows Phone. Ora aggiungeremo una SplitPage, anch'essa non disponibile per Windows Phone. Quando un dispositivo è in orientamento orizzontale, la pagina SplitPage nell'app di Windows include un riquadro destro e uno sinistro. Quando l'utente passa alla pagina nella nostra app, vedrà l'elenco di elementi di feed nel riquadro sinistro e un rendering del testo del feed selezionato nel riquadro destro. Quando il dispositivo è in orientamento verticale o la finestra non è a larghezza intera, la pagina divisa usa oggetti VisualState per comportarsi come se fosse due pagine distinte. Questo meccanismo è noto come "navigazione all'interno di pagine logiche" nel codice.

  1. Inizia con il codice seguente, che è il codice XAML per una pagina divisa di base che era il modello predefinito nei progetti di Windows 8.

    <Page
        x:Name="pageRoot"
        x:Class="SimpleBlogReader.SplitPage"
        DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:SimpleBlogReader"
        xmlns:common="using:SimpleBlogReader.Common"
        xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    
    
        <Page.Resources>
            <!-- Collection of items displayed by this page -->
            <CollectionViewSource
            x:Name="itemsViewSource"
            Source="{Binding Items}"/>
        </Page.Resources>
    
        <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"
                              Click="fwdButton_Click"/>
                </Grid>
            </AppBar>
        </Page.TopAppBar>
        <!--
            This grid acts as a root panel for the page that defines two rows:
            * Row 0 contains the back button and page title
            * Row 1 contains the rest of the page layout
        -->
        <Grid Style="{StaticResource WindowsBlogLayoutRootStyle}">
            <Grid.ChildrenTransitions>
                <TransitionCollection>
                    <EntranceThemeTransition/>
                </TransitionCollection>
            </Grid.ChildrenTransitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="140"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition x:Name="primaryColumn" Width="420"/>
                <ColumnDefinition x:Name="secondaryColumn" Width="*"/>
            </Grid.ColumnDefinitions>
    
            <!-- Back button and page title -->
            <Grid x:Name="titlePanel" Grid.ColumnSpan="1">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="120"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Button x:Name="backButton" Margin="39,59,39,0" 
                        Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
                            Style="{StaticResource NavigationBackButtonNormalStyle}"
                            VerticalAlignment="Top"
                            AutomationProperties.Name="Back"
                            AutomationProperties.AutomationId="BackButton"
                            AutomationProperties.ItemType="Navigation Button"/>
    
                <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{Binding Title}" 
                           Style="{StaticResource HeaderTextBlockStyle}"
                           IsHitTestVisible="false" TextWrapping="NoWrap" 
                           VerticalAlignment="Bottom" Padding="10,10,10,10" Margin="0,0,30,40">
                    <TextBlock.Transitions>
                        <TransitionCollection>
                            <ContentThemeTransition/>
                        </TransitionCollection>
                    </TextBlock.Transitions>
                </TextBlock>
            </Grid>
    
            <!-- 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">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <Grid Margin="6">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}" Width="60" Height="60">
                                <Image Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
                            </Border>
                            <StackPanel Grid.Column="1" Margin="10,0,0,0">
                                <TextBlock Text="{Binding Title}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap" MaxHeight="40"/>
                                <TextBlock Text="{Binding Subtitle}" Style="{StaticResource CaptionTextBlockStyle}" TextWrapping="NoWrap"/>
                            </StackPanel>
                        </Grid>
                    </DataTemplate>
                </ListView.ItemTemplate>
                <ListView.ItemContainerStyle>
                    <Style TargetType="FrameworkElement">
                        <Setter Property="Margin" Value="0,0,0,10"/>
                    </Style>
                </ListView.ItemContainerStyle>
            </ListView>
    
    
            <!-- Details for selected item -->
            <ScrollViewer
                x:Name="itemDetail"
                AutomationProperties.AutomationId="ItemDetailScrollViewer"
                Grid.Column="1"
                Grid.RowSpan="2"
                Padding="60,0,66,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">
                        <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>
                    <TextBlock Grid.Row="2" Grid.ColumnSpan="2" Margin="0,20,0,0" Text="{Binding Content}" Style="{StaticResource BodyTextBlockStyle}"/>
                </Grid>
            </ScrollViewer>
    
    
            <VisualStateManager.VisualStateGroups>
    
                <!-- Visual states reflect the application's view state -->
                <VisualStateGroup x:Name="ViewStates">
                    <VisualState x:Name="PrimaryView" />
                    <VisualState x:Name="SinglePane">
                        <Storyboard>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="primaryColumn" 
                                Storyboard.TargetProperty="Width">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="*"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="secondaryColumn" 
                                Storyboard.TargetProperty="Width">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="itemDetail" 
                                Storyboard.TargetProperty="Visibility">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="ItemListView" 
                                Storyboard.TargetProperty="Padding">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="120,0,90,60"/>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                    <!--
                        When an item is selected and only one pane is shown the details display requires more extensive changes:
                         * Hide the master list and the column it was in
                         * Move item details down a row to make room for the title
                         * Move the title directly above the details
                         * Adjust padding for details
                     -->
                    <VisualState x:Name="SinglePane_Detail">
                        <Storyboard>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="primaryColumn" 
                                Storyboard.TargetProperty="Width">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="secondaryColumn" 
                                Storyboard.TargetProperty="Width">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="*"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="ItemListView" 
                                Storyboard.TargetProperty="Visibility">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="titlePanel" 
                                Storyboard.TargetProperty="(Grid.Column)">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="itemDetail" 
                                Storyboard.TargetProperty="Padding">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="10,0,10,0"/>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
        </Grid>
    </Page>
    
  2. Per la pagina predefinita sono già stati impostati il contesto dati e CollectionViewSource.

    Modifichiamo la griglia titlePanel in modo che si estenda su due colonne. In questo modo il titolo del feed viene visualizzato lungo l'intera larghezza dello schermo:

    <Grid x:Name="titlePanel" Grid.ColumnSpan="2">
    
  3. Cerca ora il TextBlock pageTitle in questa stessa griglia e modifica Binding da Title a Feed.Title.

    Text="{Binding Feed.Title}"
    
  4. Cerca il commento "Vertical scrolling item list" e sostituisci il controllo predefinito ListView con questo:

            <!-- 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="10,0,0,60"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                IsSwipeEnabled="False"
                SelectionChanged="ItemListView_SelectionChanged"
                ItemTemplate="{StaticResource ListItemTemplate}">
    
                <ListView.ItemContainerStyle>
                    <Style TargetType="FrameworkElement">
                        <Setter Property="Margin" Value="0,0,0,10"/>
                    </Style>
                </ListView.ItemContainerStyle>
            </ListView>
    
  5. Il riquadro dei dettagli di una SplitPage può contenere qualsiasi cosa. In questa app inseriremo un RichTextBlock e visualizzeremo una versione di testo semplice del post di blog. Possiamo usare una funzione di utilità fornita dall'API di Windows per analizzare il codice HTML da FeedItem e restituire Platform::String. Useremo quindi la nostra classe di utilità per suddividere la stringa restituita in paragrafi e creare elementi di testo RTF. Questa visualizzazione non mostrerà alcuna immagine ma si carica velocemente e, se vuoi estendere questa app, hai la possibilità di aggiungere successivamente un'opzione per consentire all'utente di modificare il tipo e la dimensione del carattere.

    Trova l'elemento ScrollViewer sotto il commento "Details for selected item" ed eliminalo. Incolla quindi questo markup:

            <!-- Details for selected item -->
            <Grid x:Name="itemDetailGrid" 
                  Grid.Row="1"
                  Grid.Column="1"
                  Margin="10,10,10,10">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>
                <TextBlock x:Name="itemTitle" Margin="10,10,10,10" 
                           DataContext="{Binding SelectedItem, ElementName=itemListView}" 
                           Text="{Binding Title}" 
                           Style="{StaticResource SubheaderTextBlockStyle}"/>
                <ScrollViewer
                x:Name="itemDetail"
                AutomationProperties.AutomationId="ItemDetailScrollViewer"
                Grid.Row="1"
                Padding="20,20,20,20"
                DataContext="{Binding SelectedItem, ElementName=itemListView}"
                HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
                ScrollViewer.HorizontalScrollMode="Disabled" ScrollViewer.VerticalScrollMode="Enabled"
                ScrollViewer.ZoomMode="Disabled" Margin="4,0,-4,0">
                    <Border x:Name="contentViewBorder" BorderBrush="#FFFE5815" 
                            Background="Honeydew" BorderThickness="5" Grid.Row="1">
                        <RichTextBlock x:Name="BlogTextBlock" Foreground="Black" 
                                       FontFamily="Lucida Sans" 
                                       FontSize="32"
                                       Margin="20,20,20,20">                        
                        </RichTextBlock>
                    </Border>
                </ScrollViewer>
            </Grid>
    

LoadState e SaveState (SplitPage dell'app di Windows)

  1. Sostituisci la pagina SplitPage creata con il codice seguente.

    SplitPage.xaml.h dovrebbe avere l'aspetto seguente:

    //
    // SplitPage.xaml.h
    // Declaration of the SplitPage class
    //
    
    #pragma once
    
    #include "SplitPage.g.h"
    #include "Common\NavigationHelper.h"
    
    namespace SimpleBlogReader
    {
        namespace WFC = Windows::Foundation::Collections;
        namespace WUIX = Windows::UI::Xaml;
        namespace WUIXNav = Windows::UI::Xaml::Navigation;
        namespace WUIXDoc = Windows::UI::Xaml::Documents;
        namespace WUIXControls = Windows::UI::Xaml::Controls;
    
           /// <summary>
           /// A page that displays a group title, a list of items within the group, and details for the
           /// currently selected item.
           /// </summary>
           [Windows::Foundation::Metadata::WebHostHidden]
           public ref class SplitPage sealed
           {
           public:
                  SplitPage();
    
                  /// <summary>
                  /// This can be changed to a strongly typed view model.
                  /// </summary>
                  property WFC::IObservableMap<Platform::String^, 
                Platform::Object^>^ DefaultViewModel
                  {
                         WFC::IObservableMap<Platform::String^, Platform::Object^>^  get();
                  }
    
                  /// <summary>
                  /// NavigationHelper is used on each page to aid in navigation and 
                  /// process lifetime management
                  /// </summary>
                  property Common::NavigationHelper^ NavigationHelper
                  {
                         Common::NavigationHelper^ get();
                  }
           protected:
                  virtual void OnNavigatedTo(WUIXNav::NavigationEventArgs^ e) override;
                  virtual void OnNavigatedFrom(WUIXNav::NavigationEventArgs^ e) override;
    
           private:
                  void LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e);
                  void SaveState(Object^ sender, Common::SaveStateEventArgs^ e);
                  bool CanGoBack();
                  void GoBack();
    
    #pragma region Logical page navigation
    
                  // The split page isdesigned so that when the Window does have enough space to show
                  // both the list and the dteails, only one pane will be shown at at time.
                  //
                  // This is all implemented with a single physical page that can represent two logical
                  // pages.  The code below achieves this goal without making the user aware of the
                  // distinction.
    
                  void Window_SizeChanged(Platform::Object^ sender, 
                Windows::UI::Core::WindowSizeChangedEventArgs^ e);
                  void ItemListView_SelectionChanged(Platform::Object^ sender, 
                WUIXControls::SelectionChangedEventArgs^ e);
                  bool UsingLogicalPageNavigation();
                  void InvalidateVisualState();
                  Platform::String^ DetermineVisualState();
    
    #pragma endregion
    
                  static Windows::UI::Xaml::DependencyProperty^ _defaultViewModelProperty;
                  static Windows::UI::Xaml::DependencyProperty^ _navigationHelperProperty;
                  static const int MinimumWidthForSupportingTwoPanes = 768;
    
            void fwdButton_Click(Platform::Object^ sender, WUIX::RoutedEventArgs^ e);
            void pageRoot_SizeChanged(Platform::Object^ sender, WUIX::SizeChangedEventArgs^ e);
           };
    }
    

    Per SplitPage.xaml.cpp usa il codice seguente come punto di partenza. Ciò ti permette di implementare una pagina divisa e aggiungere lo stesso supporto di NavigationHelper e SuspensionManager aggiunto alle altre pagine, nonché lo stesso gestore dell'evento SizeChanged aggiunto a una pagina precedente.

    //
    // SplitPage.xaml.cpp
    // Implementation of the SplitPage class
    //
    
    #include "pch.h"
    #include "SplitPage.xaml.h"
    
    using namespace SimpleBlogReader;
    using namespace SimpleBlogReader::Common;
    
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace concurrency;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::UI::Core;
    using namespace Windows::UI::ViewManagement;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Controls::Primitives;
    using namespace Windows::UI::Xaml::Data;
    using namespace Windows::UI::Xaml::Documents;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Interop;
    using namespace Windows::UI::Xaml::Media;
    using namespace Windows::UI::Xaml::Navigation;
    
    // The Split Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234234
    
    SplitPage::SplitPage()
    {
        InitializeComponent();
        SetValue(_defaultViewModelProperty, ref new Map<String^, Object^>(std::less<String^>()));
        auto navigationHelper = ref new Common::NavigationHelper(this,
            ref new Common::RelayCommand(
            [this](Object^) -> bool
        {
            return CanGoBack();
        },
            [this](Object^) -> void
        {
            GoBack();
        }
            )
            );
        SetValue(_navigationHelperProperty, navigationHelper);
        navigationHelper->LoadState += 
            ref new Common::LoadStateEventHandler(this, &SplitPage::LoadState);
        navigationHelper->SaveState += 
            ref new Common::SaveStateEventHandler(this, &SplitPage::SaveState);
    
        ItemListView->SelectionChanged += 
            ref new SelectionChangedEventHandler(this, &SplitPage::ItemListView_SelectionChanged);
        Window::Current->SizeChanged += 
            ref new WindowSizeChangedEventHandler(this, &SplitPage::Window_SizeChanged);
        InvalidateVisualState();
    
    }
    
    DependencyProperty^ SplitPage::_defaultViewModelProperty =
    DependencyProperty::Register("DefaultViewModel",
    TypeName(IObservableMap<String^, Object^>::typeid), TypeName(SplitPage::typeid), nullptr);
    
    /// <summary>
    /// used as a trivial view model.
    /// </summary>
    IObservableMap<String^, Object^>^ SplitPage::DefaultViewModel::get()
    {
        return safe_cast<IObservableMap<String^, Object^>^>(GetValue(_defaultViewModelProperty));
    }
    
    DependencyProperty^ SplitPage::_navigationHelperProperty =
    DependencyProperty::Register("NavigationHelper",
    TypeName(Common::NavigationHelper::typeid), TypeName(SplitPage::typeid), nullptr);
    
    /// <summary>
    /// Gets an implementation of <see cref="NavigationHelper"/> designed to be
    /// used as a trivial view model.
    /// </summary>
    Common::NavigationHelper^ SplitPage::NavigationHelper::get()
    {
        //        return _navigationHelper;
        return safe_cast<Common::NavigationHelper^>(GetValue(_navigationHelperProperty));
    }
    
    #pragma region Page state management
    
    /// <summary>
    /// Populates the page with content passed during navigation.  Any saved state is also
    /// provided when recreating a page from a prior session.
    /// </summary>
    /// <param name="navigationParameter">The parameter value passed to
    /// <see cref="Frame::Navigate(Type, Object)"/> when this page was initially requested.
    /// </param>
    /// <param name="pageState">A map of state preserved by this page during an earlier
    /// session.  This will be null the first time a page is visited.</param>
    void SplitPage::LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e)
    {
        if (!this->DefaultViewModel->HasKey("Feed"))
        {
            auto app = safe_cast<App^>(App::Current);
            app->GetCurrentFeedAsync().then([this, app, e](FeedData^ fd)
            {
                // Insert into the ViewModel for this page to initialize itemsViewSource->View
                this->DefaultViewModel->Insert("Feed", fd);
                this->DefaultViewModel->Insert("Items", fd->Items);
    
                if (e->PageState == nullptr)
                {
                    // 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);
                    }
                }
                else
                {
                    auto itemUri = safe_cast<String^>(e->PageState->Lookup("SelectedItemUri"));
                    auto app = safe_cast<App^>(App::Current);
                    auto selectedItem = app->GetFeedItem(fd, itemUri);
    
                    if (selectedItem != nullptr)
                    {
                        this->itemsViewSource->View->MoveCurrentTo(selectedItem);
                    }
                }
            }, task_continuation_context::use_current());
        }
    }
    
    /// <summary>
    /// Preserves state associated with this page in case the application is suspended or the
    /// page is discarded from the navigation cache.  Values must conform to the serialization
    /// requirements of <see cref="SuspensionManager::SessionState"/>.
    /// </summary>
    /// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
    /// <param name="e">Event data that provides an empty dictionary to be populated with
    /// serializable state.</param>
    void SplitPage::SaveState(Platform::Object^ sender, Common::SaveStateEventArgs^ e)
    {
        if (itemsViewSource->View != nullptr)
        {
            auto selectedItem = itemsViewSource->View->CurrentItem;
            if (selectedItem != nullptr)
            {
                auto feedItem = safe_cast<FeedItem^>(selectedItem);
                e->PageState->Insert("SelectedItemUri", feedItem->Link->AbsoluteUri);
            }
        }
    }
    
    #pragma endregion
    
    #pragma region Logical page navigation
    
    // Visual state management typically reflects the four application view states directly (full
    // screen landscape and portrait plus snapped and filled views.)  The split page is designed so
    // that the snapped and portrait view states each have two distinct sub-states: either the item
    // list or the details are displayed, but not both at the same time.
    //
    // This is all implemented with a single physical page that can represent two logical pages.
    // The code below achieves this goal without making the user aware of the distinction.
    
    /// <summary>
    /// Invoked to determine whether the page should act as one logical page or two.
    /// </summary>
    /// <returns>True when the current view state is portrait or snapped, false
    /// otherwise.</returns>
    bool SplitPage::CanGoBack()
    {
        if (UsingLogicalPageNavigation() && ItemListView->SelectedItem != nullptr)
        {
            return true;
        }
        else
        {
            return NavigationHelper->CanGoBack();
        }
    }
    
    void SplitPage::GoBack()
    {
        if (UsingLogicalPageNavigation() && ItemListView->SelectedItem != nullptr)
        {
            // When logical page navigation is in effect and there's a selected item that
            // item's details are currently displayed.  Clearing the selection will return to
            // the item list.  From the user's point of view this is a logical backward
            // navigation.
            ItemListView->SelectedItem = nullptr;
        }
        else
       {
            NavigationHelper->GoBack();
        }
    }
    
    /// <summary>
    /// Invoked with the Window changes size
    /// </summary>
    /// <param name="sender">The current Window</param>
    /// <param name="e">Event data that describes the new size of the Window</param>
    void SplitPage::Window_SizeChanged(Platform::Object^ sender, WindowSizeChangedEventArgs^ e)
    {
        InvalidateVisualState();
    }
    
    /// <summary>
    /// Invoked when an item within the list is selected.
    /// </summary>
    /// <param name="sender">The GridView displaying the selected item.</param>
    /// <param name="e">Event data that describes how the selection was changed.</param>
    void SplitPage::ItemListView_SelectionChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e)
    {
           if (UsingLogicalPageNavigation())
           {
                  InvalidateVisualState();
           }
    }
    
    /// <summary>
    /// Invoked to determine whether the page should act as one logical page or two.
    /// </summary>
    /// <returns>True if the window should show act as one logical page, false
    /// otherwise.</returns>
    bool SplitPage::UsingLogicalPageNavigation()
    {
        return Window::Current->Bounds.Width <= MinimumWidthForSupportingTwoPanes;
    }
    
    void SplitPage::InvalidateVisualState()
    {
        auto visualState = DetermineVisualState();
        VisualStateManager::GoToState(this, visualState, false);
        NavigationHelper->GoBackCommand->RaiseCanExecuteChanged();
    }
    
    /// <summary>
    /// Invoked to determine the name of the visual state that corresponds to an application
    /// view state.
    /// </summary>
    /// <returns>The name of the desired visual state.  This is the same as the name of the
    /// view state except when there is a selected item in portrait and snapped views where
    /// this additional logical page is represented by adding a suffix of _Detail.</returns>
    Platform::String^ SplitPage::DetermineVisualState()
    {
        if (!UsingLogicalPageNavigation())
            return "PrimaryView";
    
        // Update the back button's enabled state when the view state changes
        auto logicalPageBack = UsingLogicalPageNavigation() 
            && ItemListView->SelectedItem != nullptr;
    
        return logicalPageBack ? "SinglePane_Detail" : "SinglePane";
    }
    
    #pragma endregion
    
    #pragma region Navigation support
    
    /// The methods provided in this section are simply used to allow
    /// NavigationHelper to respond to the page's navigation methods.
    /// 
    /// Page specific logic should be placed in event handlers for the  
    /// <see cref="NavigationHelper::LoadState"/>
    /// and <see cref="NavigationHelper::SaveState"/>.
    /// The navigation parameter is available in the LoadState method 
    /// in addition to page state preserved during an earlier session.
    
    void SplitPage::OnNavigatedTo(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedTo(e);
    }
    
    void SplitPage::OnNavigatedFrom(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedFrom(e);
    
    }
    #pragma endregion
    
    void SimpleBlogReader::SplitPage::fwdButton_Click(Platform::Object^ sender, RoutedEventArgs^ e)
    {
        // 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 itemUri = safe_cast<String^>(selectedItem->Link->AbsoluteUri);
            this->Frame->Navigate(WebViewerPage::typeid, itemUri);
        }
    }
    
    /// <summary>
    /// 
    /// 
    /// </summary>
    void SimpleBlogReader::SplitPage::pageRoot_SizeChanged(
        Platform::Object^ sender,
        SizeChangedEventArgs^ e)
    {
        if (e->NewSize.Height / e->NewSize.Width >= 1)
        {
            VisualStateManager::GoToState(this, "SinglePane", true);
        }
        else
        {
            VisualStateManager::GoToState(this, "PrimaryView", true);
        }
    }
    
  2. In SplitPage.xaml.cpp aggiungi questa direttiva using:

    using namespace Windows::UI::Xaml::Documents;
    
  3. Sostituisci ora LoadState e SaveState con questo codice:

    void SplitPage::LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e)
    {
        if (!this->DefaultViewModel->HasKey("Feed"))
        {
            auto app = safe_cast<App^>(App::Current);
            app->GetCurrentFeedAsync().then([this, app, e](FeedData^ fd)
            {
                // Insert into the ViewModel for this page to initialize itemsViewSource->View
                this->DefaultViewModel->Insert("Feed", fd);
                this->DefaultViewModel->Insert("Items", fd->Items);
    
                if (e->PageState == nullptr)
                {
                    // 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);
                    }
                }
                else
                {
                    auto itemUri = safe_cast<String^>(e->PageState->Lookup("SelectedItemUri"));
                    auto app = safe_cast<App^>(App::Current);
                    auto selectedItem = GetFeedItem(fd, itemUri);
    
                    if (selectedItem != nullptr)
                    {
                        this->itemsViewSource->View->MoveCurrentTo(selectedItem);
                    }
                }
            }, task_continuation_context::use_current());
        }
    }
    
    /// <summary>
    /// Preserves state associated with this page in case the application is suspended or the
    /// page is discarded from the navigation cache.  Values must conform to the serialization
    /// requirements of <see cref="SuspensionManager::SessionState"/>.
    /// </summary>
    /// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
    /// <param name="e">Event data that provides an empty dictionary to be populated with
    /// serializable state.</param>
    void SplitPage::SaveState(Platform::Object^ sender, Common::SaveStateEventArgs^ e)
    {
        if (itemsViewSource->View != nullptr)
        {
            auto selectedItem = itemsViewSource->View->CurrentItem;
            if (selectedItem != nullptr)
            {
                auto feedItem = safe_cast<FeedItem^>(selectedItem);
                e->PageState->Insert("SelectedItemUri", feedItem->Link->AbsoluteUri);
            }
        }
    }
    

    Nota che stiamo usando il metodo GetCurrentFeedAsync che abbiamo aggiunto al progetto condiviso in precedenza. Una differenza tra questa pagina e la pagina per Windows Phone è che ora teniamo traccia dell'elemento selezionato. In SaveState inseriamo l'elemento selezionato nell'oggetto PageState, in modo che venga salvato come necessario da SuspensionManager e che sia di nuovo disponibile nell'oggetto PageState quando si chiama LoadState. Questa stringa ci servirà per cercare il FeedItem corrente nel feed corrente.

Gestori eventi (SplitPage dell'app di Windows)

Quando cambia l'elemento selezionato, il riquadro dei dettagli userà la classe TextHelper per il rendering del testo.

  1. In SplitPage.xaml.cpp aggiungi queste direttive #include:

    #include "TextHelper.h"
    #include "WebViewerPage.xaml.h"
    
  2. Sostituisci lo stub del gestore dell'evento SelectionChanged predefinito con questo:

    void SimpleBlogReader::SplitPage::ItemListView_SelectionChanged(
        Platform::Object^ sender,
        SelectionChangedEventArgs^ e)
    {
        if (UsingLogicalPageNavigation())
        {
            InvalidateVisualState();
        }
    
        // 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)
        {
            BlogTextBlock->Blocks->Clear();
            TextHelper^ helper = ref new TextHelper();
            auto blocks = helper->CreateRichText(fi->Content, 
                ref new TypedEventHandler<Hyperlink^, 
                HyperlinkClickEventArgs^>(this, &SplitPage::RichTextHyperlinkClicked));
            for (auto b : blocks)
            {
                BlogTextBlock->Blocks->Append(b);
            }
        }
    }
    

    Questa funzione specifica un callback che verrà passato a un collegamento ipertestuale creato nel testo RTF.

  3. Aggiungi questa funzione membro privata in SplitPage.xaml.h:

    void RichTextHyperlinkClicked(Windows::UI::Xaml::Documents::Hyperlink^ link, 
        Windows::UI::Xaml::Documents::HyperlinkClickEventArgs^ args);
    
  4. E questa implementazione in SplitPage.xaml.cpp:

    /// <summary>
    ///  Navigate to the appropriate destination page, and configure the new page
    ///  by passing required information as a navigation parameter.
    /// </summary>
    void SplitPage::RichTextHyperlinkClicked(
        Hyperlink^ hyperLink,
        HyperlinkClickEventArgs^ args)
    {
    
        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 itemUri = safe_cast<String^>(selectedItem->Link->AbsoluteUri);
            this->Frame->Navigate(WebViewerPage::typeid, itemUri);
        }
    }
    

    Questa funzione fa riferimento a sua volta alla pagina successiva nello stack di navigazione. Ora puoi premere F5 e visualizzare il testo che viene aggiornato quando cambia la selezione. Esegui nel simulatore e ruota il dispositivo virtuale per verificare che gli oggetti VisualState predefiniti gestiscono gli orientamenti verticale e orizzontale esattamente come previsto. Fai clic sul testo Link nel testo nel blog e passa a WebViewerPage. Naturalmente non è ancora presente alcun contenuto. Ci occuperemo di questo dopo aver aggiornato allo stesso livello anche il progetto Windows Phone.

Informazioni sugli spostamenti all'indietro

Probabilmente avrai notato che nell'app di Windows SplitPage include un pulsante per lo spostamento all'indietro che riporta a MainPage senza che tu debba scrivere ulteriore codice. Nel telefono, la funzionalità del pulsante Indietro viene fornita con il pulsante hardware Indietro e non tramite pulsanti software. Gli spostamenti con il pulsante Indietro nel telefono vengono gestiti dalla classe NavigationHelper nella cartella Common. Cerca "BackPressed" (CTRL+MAIUSC+F) nella tua soluzione per trovare il codice rilevante. Anche in questo caso, non è necessario fare altro per ottenere questa funzionalità.

Parte 9: aggiungere una visualizzazione Web del post selezionato.

L'ultima pagina che aggiungeremo è quella usata per visualizzare il post di blog nella pagina Web originale. Per un lettore a volte possono essere utili anche le immagini. Lo svantaggio della visualizzazione delle pagine Web è che il testo può essere difficile da leggere sullo schermo del telefono e non tutte le pagine Web sono formattate correttamente per i dispositivi mobili. Talvolta i margini si estendono oltre lo schermo e la lettura richiede molto scorrimento orizzontale. La nostra pagina WebViewerPage è relativamente semplice. Aggiungeremo solo un controllo WebView nella pagina, che si occuperà di tutte le operazioni. Inizieremo con il progetto per il telefono:

Hh465045.wedge(it-it,WIN.10).gifAggiungere il codice XAML (WebViewerPage dell'app di Windows Phone)

  • In WebViewerPage.xaml aggiungi il pannello del titolo e la griglia contentRoot:

            <!-- TitlePanel -->
            <StackPanel Grid.Row="0" Margin="10,10,10,10">
                <TextBlock Text="{StaticResource AppName}" 
                           Style="{ThemeResource TitleTextBlockStyle}" 
                           Typography.Capitals="SmallCaps"/>
            </StackPanel>
    
            <!--TODO: Content should be placed within the following grid-->
            <Grid Grid.Row="1" x:Name="ContentRoot">
                <!-- Back button and page title -->
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
    
                <!--This will render while web page is still downloading, 
                indicating that something is happening-->
                <TextBlock x:Name="pageTitle" Text="{Binding Title}" Grid.Column="1" 
                           IsHitTestVisible="false" 
                           TextWrapping="WrapWholeWords"  
                           VerticalAlignment="Center"  
                           HorizontalAlignment="Center"  
                           Margin="40,20,40,20"/>
    
            </Grid>
    

Hh465045.wedge(it-it,WIN.10).gifLoadState e SaveState (WebViewerPage dell'app di Windows Phone)

  1. Inizia a WebViewerPage come tutte le altre pagine, ovvero specificando il supporto di NavigationHelper e DefaultItems nel file WebViewerPage.xaml.h e l'implementazione in WebViewerPage.xaml.cpp.

    WebViewerPage.xaml.h dovrebbe venire avviato nel modo seguente:

    //
    // WebViewerPage.xaml.h
    // Declaration of the WebViewerPage class
    //
    
    #pragma once
    
    #include "WebViewerPage.g.h"
    #include "Common\NavigationHelper.h"
    
    namespace SimpleBlogReader
    {
        namespace WFC = Windows::Foundation::Collections;
        namespace WUIX = Windows::UI::Xaml;
        namespace WUIXNav = Windows::UI::Xaml::Navigation;
        namespace WUIXControls = Windows::UI::Xaml::Controls;
    
                    /// <summary>
                    /// A basic page that provides characteristics common to most applications.
                    /// </summary>
                    [Windows::Foundation::Metadata::WebHostHidden]
                    public ref class WebViewerPage sealed
                    {
                    public:
                                    WebViewerPage();
    
                                    /// <summary>
                                    /// This can be changed to a strongly typed view model.
                                    /// </summary>
                                    property WFC::IObservableMap<Platform::String^, Platform::Object^>^ DefaultViewModel
                                    {
                                                    WFC::IObservableMap<Platform::String^, Platform::Object^>^  get();
                                    }
    
                                    /// <summary>
                                    /// NavigationHelper is used on each page to aid in navigation and 
                                    /// process lifetime management
                                    /// </summary>
                                    property Common::NavigationHelper^ NavigationHelper
                                    {
                                                    Common::NavigationHelper^ get();
                                    }
    
                    protected:
                                    virtual void OnNavigatedTo(WUIXNav::NavigationEventArgs^ e) override;
                                    virtual void OnNavigatedFrom(WUIXNav::NavigationEventArgs^ e) override;
    
                    private:
                                    void LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e);
                                    void SaveState(Platform::Object^ sender, Common::SaveStateEventArgs^ e);
    
                                    static Windows::UI::Xaml::DependencyProperty^ _defaultViewModelProperty;
                                    static Windows::UI::Xaml::DependencyProperty^ _navigationHelperProperty;
    
    
                    };
    }
    

    WebViewerPage.xaml.cpp dovrebbe venire avviato nel modo seguente:

    //
    // WebViewerPage.xaml.cpp
    // Implementation of the WebViewerPage class
    //
    
    #include "pch.h"
    #include "WebViewerPage.xaml.h"
    
    using namespace SimpleBlogReader;
    using namespace concurrency;
    
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Controls::Primitives;
    using namespace Windows::UI::Xaml::Data;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Interop;
    using namespace Windows::UI::Xaml::Media;
    using namespace Windows::UI::Xaml::Media::Animation;
    using namespace Windows::UI::Xaml::Navigation;
    
    // The Basic Page item template is documented at 
    // https://go.microsoft.com/fwlink/?LinkId=234237
    
    WebViewerPage::WebViewerPage()
    {
        InitializeComponent();
        SetValue(_defaultViewModelProperty, ref new Map<String^, Object^>(std::less<String^>()));
        auto navigationHelper = ref new Common::NavigationHelper(this);
        SetValue(_navigationHelperProperty, navigationHelper);
        navigationHelper->LoadState += ref new Common::LoadStateEventHandler(this, &WebViewerPage::LoadState);
        navigationHelper->SaveState += ref new Common::SaveStateEventHandler(this, &WebViewerPage::SaveState);
    }
    
    DependencyProperty^ WebViewerPage::_defaultViewModelProperty =
    DependencyProperty::Register("DefaultViewModel",
    TypeName(IObservableMap<String^, Object^>::typeid), TypeName(WebViewerPage::typeid), nullptr);
    
    /// <summary>
    /// used as a trivial view model.
    /// </summary>
    IObservableMap<String^, Object^>^ WebViewerPage::DefaultViewModel::get()
    {
        return safe_cast<IObservableMap<String^, Object^>^>(GetValue(_defaultViewModelProperty));
    }
    
    DependencyProperty^ WebViewerPage::_navigationHelperProperty =
    DependencyProperty::Register("NavigationHelper",
    TypeName(Common::NavigationHelper::typeid), TypeName(WebViewerPage::typeid), nullptr);
    
    /// <summary>
    /// Gets an implementation of <see cref="NavigationHelper"/> designed to be
    /// used as a trivial view model.
    /// </summary>
    Common::NavigationHelper^ WebViewerPage::NavigationHelper::get()
    {
        return safe_cast<Common::NavigationHelper^>(GetValue(_navigationHelperProperty));
    }
    
    #pragma region Navigation support
    
    /// The methods provided in this section are simply used to allow
    /// NavigationHelper to respond to the page's navigation methods.
    /// 
    /// Page specific logic should be placed in event handlers for the  
    /// <see cref="NavigationHelper::LoadState"/>
    /// and <see cref="NavigationHelper::SaveState"/>.
    /// The navigation parameter is available in the LoadState method 
    /// in addition to page state preserved during an earlier session.
    
    void WebViewerPage::OnNavigatedTo(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedTo(e);
    }
    
    void WebViewerPage::OnNavigatedFrom(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedFrom(e);
    }
    
    #pragma endregion
    
    
  2. In WebViewerPage.xaml.h aggiungi questa variabile membro privata:

    Windows::Foundation::Uri^ m_feedItemUri;
    
  3. In WebViewerPage.xaml.cpp sostituisci LoadState e SaveState con questo codice:

    void WebViewerPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
        // Run the PopInThemeAnimation. 
        Storyboard^ sb = dynamic_cast<Storyboard^>(this->FindName("PopInStoryboard"));
        if (sb != nullptr)
        {
            sb->Begin();
        }
    
        if (e->PageState == nullptr)
        {
            m_feedItemUri = safe_cast<String^>(e->NavigationParameter);
            contentView->Navigate(ref new Uri(m_feedItemUri));
        }
        // We are resuming from suspension:
        else
        {
            m_feedItemUri = safe_cast<String^>(e->PageState->Lookup("FeedItemUri"));
            contentView->Navigate(ref new Uri(m_feedItemUri));
        }
    }
    
    void WebViewerPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
        (void)e; // Unused parameter
        e->PageState->Insert("FeedItemUri", m_feedItemUri);
    }
    

    Nota l'animazione all'inizio della funzione. Puoi trovare altre informazioni sulle animazioni in Windows Developer Center. Nota ancora una volta che dobbiamo gestire i due possibili modi con i quali potremmo raggiungere questa pagina. In caso di ripresa, dobbiamo cercare lo stato.

Ecco fatto! Premi F5 e ora puoi spostarti da TextViewerPage a WebViewerPage.

Torniamo al progetto Windows. L'operazione sarà molto simile a quello che abbiamo appena fatto per il telefono.

Hh465045.wedge(it-it,WIN.10).gifAggiungere il codice XAML (WebViewerPage dell'app di Windows)

  1. In WebViewerPage.xaml aggiungi un evento SizeChanged all'elemento Page e chiamalo pageRoot_SizeChanged. Posiziona il punto di inserimento al suo interno e premi F12 per generare il code-behind.

  2. Trova la griglia "Back button and page title" ed elimina il TextBlock. Il titolo della pagina verrà visualizzato nella pagina Web, quindi non è necessario che occupi spazio qui.

  3. A questo punto, subito dopo la griglia del pulsante Indietro, aggiungi il Border con WebView:

    <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" 
                    Grid.Row="1" Margin="20,20,20,20">
                <WebView x:Name="contentView" ScrollViewer.HorizontalScrollMode="Enabled"
                         ScrollViewer.VerticalScrollMode="Enabled"/>
            </Border> 
    

    Un controllo WebView esegue molte operazioni per impostazione predefinita, ma è caratterizzato da alcune peculiarità che lo rendono diverso per alcuni aspetti dagli altri controlli XAML. Se prevedi di usarlo ampiamente in un'app, è consigliabile che tu ne approfondisca la conoscenza leggendo altro materiale.

Hh465045.wedge(it-it,WIN.10).gifAggiungere la variabile membro

  1. Aggiungi la seguente dichiarazione privata in WebViewerPage.xaml.h:

    Platform::String^ m_feedItemUri;
    

Hh465045.wedge(it-it,WIN.10).gifLoadState e SaveState (WebViewerPage dell'app di Windows)

  1. Sostituisci le funzioni LoadState e SaveState con questo codice, molto simile a quello della pagina per il telefono:

    void WebViewerPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
    
        // Run the PopInThemeAnimation. 
        auto sb = dynamic_cast<Storyboard^>(this->FindName("PopInStoryboard"));
        if (sb != nullptr)
        {
            sb->Begin();
        }
    
        // We are navigating forward from SplitPage
        if (e->PageState == nullptr)
        {
            m_feedItemUri = safe_cast<String^>(e->NavigationParameter);
            contentView->Navigate(ref new Uri(m_feedItemUri));
        }
    
        // We are resuming from suspension:
        else
        {
            contentView->Navigate(
                ref new Uri(safe_cast<String^>(e->PageState->Lookup("FeedItemUri")))
                );
        }
    }
    
    void WebViewerPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
    
        // Store the info needed to reconstruct the page on back navigation,
        // or in case we are terminated.
        e->PageState->Insert("FeedItemUri", m_feedItemUri);
    }
    

    \

  2. Imposta il progetto Windows come progetto di avvio e premi F5. Quando fai clic sul collegamento in TextViewerPage dovresti passare a WebViewerPage e quando fai clic sul pulsante Indietro in WebViewerPage dovresti tornare a TextViewerPage.

Parte 10: aggiungere e rimuovere i feed

L'app è perfettamente funzionante ora sia in Windows che in Windows Phone, presupponendo che un utente non voglia mai leggere altro a parte i tre feed hardcoded nell'app. Come passaggio finale, facciamo in modo che diventi più reale consentendo all'utente di aggiungere ed eliminare i feed a propria scelta. Mostreremo alcuni feed predefiniti all'utente in modo che lo schermo non sia vuoto al primo avvio dell'app. Aggiungeremo quindi alcuni pulsanti per consentire di aggiungere ed eliminare i feed. Naturalmente, dovremo archiviare l'elenco di feed dell'utente in modo che rimanga disponibile per ogni sessione. Questo è il momento ideale per affrontare l'argomento dei dati locali dell'app.

Prima di tutto, dobbiamo archiviare alcuni feed predefiniti per il primo avvio dell'app. Invece di incorporarli nel codice, possiamo inserirli in un file di risorse stringa dove possono essere trovati da ResourceLoader. Queste risorse devono essere compilate in entrambe le app di Windows e Windows Phone, quindi creeremo il file con estensione resw nel progetto condiviso.

Hh465045.wedge(it-it,WIN.10).gifAggiungere le risorse stringa:

  1. In Esplora soluzioni seleziona il progetto condiviso, quindi fai clic con il pulsante destro del mouse e aggiungi un nuovo elemento. Nel riquadro sinistro scegli Risorsa e quindi nel riquadro centrale scegli File di risorse (.resw). (Non scegliere il file con estensione rc perché è per le app desktop). Lascia il nome predefinito o assegna qualsiasi nome. Quindi fai clic su Aggiungi.

  2. Aggiungi le coppie nome-valore seguenti:

    L'editor di risorse dovrebbe essere simile al seguente al termine.

    Risorse stringa

Hh465045.wedge(it-it,WIN.10).gifAggiungere il codice condiviso per l'aggiunta e la rimozione di feed

  1. Aggiungeremo il codice per il caricamento degli URL alla classe FeedDataSource. In feeddata.h aggiungi questa funzione membro privato in FeedDataSource:

    concurrency::task<Windows::Foundation::Collections::IVector<Platform::String^>^> GetUserURLsAsync();
    
  2. Aggiungi queste istruzioni in FeedData.cpp

    using namespace Windows::Storage;
    using namespace Windows::Storage::Streams;
    
  3. E quindi aggiungi l'implementazione:

    /// <summary>
    /// The first time the app runs, the default feed URLs are loaded from the local resources
    /// into a text file that is stored in the app folder. All subsequent additions and lookups 
    /// are against that file. The method has to return a task because the file access is an 
    /// async operation, and the call site needs to be able to continue from it with a .then method.
    /// </summary>
    
    task<IVector<String^>^> FeedDataSource::GetUserURLsAsync()
    {
    
        return create_task(ApplicationData::Current->LocalFolder->
            CreateFileAsync("Feeds.txt", CreationCollisionOption::OpenIfExists))
            .then([](StorageFile^ file)
        {
            return FileIO::ReadLinesAsync(file);
        }).then([](IVector<String^>^ t)
        {
            if (t->Size == 0)
            {
                // The data file is new, so we'll populate it with the 
                // default URLs that are stored in the apps resources.
                auto loader = ref new Resources::ResourceLoader();
    
                t->Append(loader->GetString("URL_1\n"));
                t->Append(loader->GetString("URL_2"));
                t->Append(loader->GetString("URL_3"));
    
                // Before we return the URLs, let's create the new file asynchronously 
                //  for use next time. We don't need the result of the operation now 
                // because we already have vec, so we can just kick off the task to
                // run whenever it gets scheduled.
                create_task(ApplicationData::Current->LocalFolder->
                    CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
                    .then([t](StorageFile^ file)
                {
                    OutputDebugString(L"append lines async\n");
                    FileIO::AppendLinesAsync(file, t);
                });
            }
    
            // Return the URLs
            return create_task([t]()
            {
                OutputDebugString(L"returning t\n");
                return safe_cast<IVector<String^>^>(t);
            });
        });
    }
    

    GetUserURLsAsync controllerà se il file feeds.txt esiste. In caso contrario, lo crea e aggiunge gli URL dalle risorse stringa. Qualsiasi file aggiunto dall'utente verrà inserito nel file feeds.txt. Dato che tutte le operazioni di scrittura su file sono asincrone, usiamo un'attività e una continuazione .then per garantire che le operazioni asincrone vengano completate prima di tentare l'accesso ai dati nel file.

  4. Ora sostituisci l'implementazione di InitDataSource precedente con questo codice che chiama GetUerURLsAsync:

    ///<summary>
    /// Retrieve the data for each atom or rss feed and put it into our custom data structures.
    ///</summary>
    void FeedDataSource::InitDataSource()
    {
        auto urls = GetUserURLsAsync()
            .then([this](IVector<String^>^ urls)
        {
            // Populate the list of feeds.
            SyndicationClient^ client = ref new SyndicationClient();
            for (auto url : urls)
            {
                RetrieveFeedAndInitData(url, client);
            }
        });
    }
    
  5. Le funzioni per aggiungere e rimuovere i feed sono uguali in Windows e in Windows Phone, quindi le inseriremo nella classe App. In App.xaml.h,

  6. Aggiungi questi membri interni:

    void AddFeed(Platform::String^ feedUri);
    void RemoveFeed(Platform::String^ feedUri);
    
  7. In App.xaml.cpp aggiungi questo spazio dei nomi:

    using namespace Platform::Collections;
    
  8. In App.xaml.cpp:

    void App::AddFeed(String^ feedUri)
    {
        auto feedDataSource = 
            safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
        auto client = ref new Windows::Web::Syndication::SyndicationClient();
    
        // The UI is data-bound to the items collection and will update automatically
        // after we append to the collection.
        feedDataSource->RetrieveFeedAndInitData(feedUri, client);
    
        // Add the uri to the roaming data. The API requires an IIterable so we have to 
        // put the uri in a Vector.
        Vector<String^>^ vec = ref new Vector<String^>();
        vec->Append(feedUri);
        concurrency::create_task(ApplicationData::Current->LocalFolder->
            CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
            .then([vec](StorageFile^ file)
        {
            FileIO::AppendLinesAsync(file, vec);
        });
    }
    void App::RemoveFeed(Platform::String^ feedTitle)
    {
        // Create a new list of feeds, excluding the one the user selected.
        auto feedDataSource = 
            safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
        int feedListIndex = -1;
        Vector<String^>^  newFeeds = ref new Vector<String^>();
        for (unsigned int i = 0; i < feedDataSource->Feeds->Size; ++i)
        {
            if (feedDataSource->Feeds->GetAt(i)->Title == feedTitle)
            {
                feedListIndex = i;
            }
            else
            {
                newFeeds->Append(feedDataSource->Feeds->GetAt(i)->Uri);
            }
        }
    
        // Delete the selected item from the list view and the Feeds collection.
        feedDataSource->Feeds->RemoveAt(feedListIndex);
    
        // Overwrite the old data file with the new list.
        create_task(ApplicationData::Current->LocalFolder->
            CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
            .then([newFeeds](StorageFile^ file)
        {
            FileIO::WriteLinesAsync(file, newFeeds);
        });
    }
    

Hh465045.wedge(it-it,WIN.10).gifAggiungere il markup XAML per i pulsanti per l'aggiunta e la rimozione (Windows 8.1)

  1. I pulsanti per l'aggiunta e la rimozione dei feed appartengono a MainPage. Inseriremo i pulsanti in una TopAppBar nell'app di Windows e in una BottomAppBar nell'app di Windows Phone (le app di Windows Phone non includono barre dell'app superiori). Nel progetto Windows, in MainPage.xaml: aggiungi TopAppBar subito dopo il nodo Page.Resources:

    <Page.TopAppBar>
            <CommandBar x:Name="cmdBar" IsSticky="False" Padding="10,0,10,0">
    
                <AppBarButton x:Name="addButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Icon="Add">
                    <Button.Flyout>
                        <Flyout Placement="Top">
                            <Grid>
                                <StackPanel>
                                    <TextBox x:Name="tbNewFeed" Width="400"/>
                                    <Button Click="AddFeed_Click">Add feed</Button>
                                </StackPanel>
                            </Grid>
                        </Flyout>
                    </Button.Flyout>
                </AppBarButton>
    
                <AppBarButton x:Name="removeButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Icon="Remove"
                              Click="removeFeed_Click"/>
    
                <!--These buttons appear when the user clicks the remove button to 
                signal that they want to remove a feed. Delete removes the feed(s)  
                and returns to the normal visual state and cancel just returns 
                to the normal state. -->
                <AppBarButton x:Name="deleteButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Visibility="Collapsed"
                              Icon="Delete" Click="deleteButton_Click"/>
    
                <AppBarButton x:Name="cancelButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Visibility="Collapsed"
                              Icon="Cancel"
                              Click="cancelButton_Click"/>
            </CommandBar>
        </Page.TopAppBar>
    
  2. In ognuno dei quattro nomi dei gestori dell'evento Click (add, remove, delete, cancel) posiziona il cursore sul nome del gestore e premi F12 per generare le funzioni nel code-behind.

  3. Aggiungi questo secondo VisualStateGroup all'interno dell'elemento <VisualStateManager.VisualStateGroups>:

    <VisualStateGroup x:Name="SelectionStates">
        <VisualState x:Name="Normal"/>
            <VisualState x:Name="Checkboxes">
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" 
                            Storyboard.TargetProperty="SelectionMode">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="Multiple"/>
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" 
                            Storyboard.TargetProperty="IsItemClickEnabled">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="False"/>
                    </ObjectAnimationUsingKeyFrames>                  
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="cmdBar" 
                             Storyboard.TargetProperty="IsSticky">
                         <DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
                    </ObjectAnimationUsingKeyFrames>
                 </Storyboard>
          </VisualState>
    </VisualStateGroup>
    

Hh465045.wedge(it-it,WIN.10).gifAggiungere i gestori eventi per l'aggiunta e la rimozione di feed (Windows 8.1)

  • In MainPage.xaml.cpp sostituisci i quattro stub dei gestori eventi con questo codice:

    /// <summary>
    /// Invoked when the user clicks the "add" button to add a new feed.  
    /// Retrieves the feed data, updates the UI, adds the feed to the ListView
    /// and appends it to the data file.
    /// </summary>
    void MainPage::AddFeed_Click(Object^ sender, RoutedEventArgs^ e)
    {
        auto app = safe_cast<App^>(App::Current);
        app->AddFeed(tbNewFeed->Text);
    }
    
    /// <summary>
    /// Invoked when the user clicks the remove button. This changes the grid or list
    ///  to multi-select so that clicking on an item adds a check mark to it without 
    /// any navigation action. This method also makes the "delete" and  "cancel" buttons
    /// visible so that the user can delete selected items, or cancel the operation.
    /// </summary>
    void MainPage::removeFeed_Click(Object^ sender, RoutedEventArgs^ e)
    {
        VisualStateManager::GoToState(this, "Checkboxes", false);
        removeButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        addButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        deleteButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        cancelButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
    }
    
    ///<summary>
    /// Invoked when the user presses the "trash can" delete button on the app bar.
    ///</summary>
    void SimpleBlogReader::MainPage::deleteButton_Click(Object^ sender, RoutedEventArgs^ e)
    {
    
        // Determine whether listview or gridview is active
        IVector<Object^>^ itemsToDelete;
        if (itemListView->ActualHeight > 0)
        {
            itemsToDelete = itemListView->SelectedItems;
        }
        else
        {
            itemsToDelete = itemGridView->SelectedItems;
        }
    
        for (auto item : itemsToDelete)
        {       
            // Get the feed the user selected.
            Object^ proxy = safe_cast<Object^>(item);
            FeedData^ item = safe_cast<FeedData^>(proxy);
    
            // Remove it from the data file and app-wide feed collection
            auto app = safe_cast<App^>(App::Current);
            app->RemoveFeed(item->Title);
        }
    
        VisualStateManager::GoToState(this, "Normal", false);
        removeButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        addButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        deleteButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        cancelButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
    }
    
    ///<summary>
    /// Invoked when the user presses the "X" cancel button on the app bar. Returns the app 
    /// to the state where clicking on an item causes navigation to the feed.
    ///</summary>
    void MainPage::cancelButton_Click(Object^ sender, RoutedEventArgs^ e)
    {
        VisualStateManager::GoToState(this, "Normal", false);
        removeButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        addButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        deleteButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        cancelButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
    }
    

    Premi F5 con il progetto Windows impostato come progetto di avvio. Puoi notare che ognuna di queste funzioni membro imposta la proprietà di visibilità dei pulsanti sul valore appropriato e quindi passa allo stato di visualizzazione normale.

Hh465045.wedge(it-it,WIN.10).gifAggiungere il markup XAML per i pulsanti per l'aggiunta e la rimozione (Windows 8.1)

  1. Aggiungi la barra dell'app inferiore con i pulsanti dopo il nodo Page.Resources:

     <Page.BottomAppBar>
    
            <CommandBar x:Name="cmdBar" Padding="10,0,10,0">
    
                <AppBarButton x:Name="addButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Icon="Add"
                              >
                    <Button.Flyout>
                        <Flyout Placement="Top">
                            <Grid Background="Black">
                                <StackPanel>
                                    <TextBox x:Name="tbNewFeed" Width="400"/>
                                    <Button Click="AddFeed_Click">Add feed</Button>
                                </StackPanel>
                            </Grid>
                        </Flyout>
                    </Button.Flyout>
    
                </AppBarButton>
                <AppBarButton x:Name="removeButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Icon="Remove"
                              Click="removeFeed_Click"/>
    
    
                <!--These buttons appear when the user clicks the remove button to 
                signal that they want to remove a feed. Delete removes the feed(s)  
                and returns to the normal visual state. Cancel just returns to the normal state. -->
                <AppBarButton x:Name="deleteButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Visibility="Collapsed"
                              Icon="Delete" Click="deleteButton_Click"/>
    
    
                <AppBarButton x:Name="cancelButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Visibility="Collapsed"
                              Icon="Cancel"
                              Click="cancelButton_Click"/>
            </CommandBar>
        </Page.BottomAppBar>
    
  2. Premi F12 su tutti i nomi degli eventi Click per generare il code-behind.

  3. Aggiungi le "caselle di controllo" VisualStateGroup in modo che l'intero nodo VisualStateGroups sia simile al seguente:

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="SelectionStates">
            <VisualState x:Name="Normal"/>
            <VisualState x:Name="Checkboxes">
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ItemListView" 
                                Storyboard.TargetProperty="SelectionMode">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="Multiple"/>
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ItemListView" 
                                Storyboard.TargetProperty="IsItemClickEnabled">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="False"/>
                    </ObjectAnimationUsingKeyFrames>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    

Hh465045.wedge(it-it,WIN.10).gifAggiungi i gestori eventi per i pulsanti per l'aggiunta e la rimozione dei feed (Windows Phone 8.1)

  • In MainPage.xaml.cpp (Windows Phone 8.1) sostituisci gli stub dei gestori eventi appena creati con questo codice:

    void MainPage::AddFeed_Click(Platform::Object^ sender, RoutedEventArgs^ e)
    {
        if (tbNewFeed->Text->Length() > 9)
        {
            auto app = static_cast<App^>(App::Current);
            app->AddFeed(tbNewFeed->Text);
        }
    }
    
    
    void MainPage::removeFeed_Click(Platform::Object^ sender, RoutedEventArgs^ e)
    {
        VisualStateManager::GoToState(this, "Checkboxes", false);
        removeButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        addButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        deleteButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        cancelButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
    }
    
    
    void MainPage::deleteButton_Click(Platform::Object^ sender, RoutedEventArgs^ e)
    {
        for (auto item : ItemListView->SelectedItems)
        {
            // Get the feed the user selected.
            Object^ proxy = safe_cast<Object^>(item);
            FeedData^ item = safe_cast<FeedData^>(proxy);
    
            // Remove it from the data file and app-wide feed collection
            auto app = safe_cast<App^>(App::Current);
            app->RemoveFeed(item->Title);
        }
    
        VisualStateManager::GoToState(this, "Normal", false);
        removeButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        addButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        deleteButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        cancelButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
    
    }
    
    void MainPage::cancelButton_Click(Platform::Object^ sender, RoutedEventArgs^ e)
    {
        VisualStateManager::GoToState(this, "Normal", false);
        removeButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        addButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        deleteButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        cancelButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
    }
    

    Premi F5 e prova a usare i nuovi pulsanti per aggiungere o rimuovere i feed. Per aggiungere un feed nel telefono, fai clic su un collegamento RSS in una pagina Web e quindi salvalo. Tocca quindi la casella di modifica con il nome dell'URL e quindi tocca l'icona per la copia. Torna all'app e posiziona il punto di inserimento nella casella di modifica, quindi tocca di nuovo l'icona per la copia per incollare l'URL. Il feed dovrebbe comparire nell'elenco dei feed quasi immediatamente.

    L'app SimpleBlogReader è ora utilizzabile ed è pronta per la distribuzione nel dispositivo Windows.

Per distribuire l'app nel tuo telefono, devi prima di tutto registrarlo come descritto in Registrare il dispositivo Windows Phone.

Hh465045.wedge(it-it,WIN.10).gifPer distribuire l'app in un dispositivo Windows Phone sbloccato

  1. Crea una build di rilascio.

    Compilazione di rilascio C++ VS 2013

  2. Nel menu principale scegli Progetto | Store | Crea pacchetti dell'applicazione. In questo esercizio NON eseguirai la distribuzione nello Store. Accetta le impostazioni predefinite nella schermata successiva, a meno che non sia necessario modificarle.

  3. Se i pacchetti vengono creati correttamente, ti verrà richiesto di eseguire il Kit di certificazione app Windows (WACK). Puoi eseguire questa verifica per assicurarti che l'app non abbia eventuali difetti nascosti che potrebbero impedirne l'accettazione nello Store. Dato che non eseguiremo la distribuzione nello Store, questo passaggio è facoltativo.

  4. Nel menu principale scegli Strumenti | Windows Phone 8.1 | Distribuzione applicazione. Viene visualizzata la procedura guidata Distribuzione applicazione e nella prima schermata Destinazione dovrebbe essere indicato "Dispositivo". Fai clic sul pulsante Sfoglia per passare alla cartella AppPackages nell'albero del progetto, allo stesso livello delle cartelle Debug e Release. Individua il pacchetto più recente in questa cartella (se sono presenti più pacchetti), fai doppio clic su di esso e quindi fai clic sul file appx o appxbundle all'interno dello stesso.

  5. Assicurati che il telefono sia collegato al computer e che non sia bloccato dalla schermata di blocco. Seleziona il pulsante Distribuisci nella procedura guidata e attendi il completamento della distribuzione. Il messaggio di conferma del completamento della distribuzione dovrebbe essere visualizzato entro pochi secondi. Individua l'app nell'elenco delle applicazioni nel telefono e tocca l'app per eseguirla.

    Nota: l'aggiunta di nuovi URL può non essere del tutto intuitiva inizialmente. Cerca un URL che vuoi aggiungere e quindi tocca il collegamento. Quando richiesto conferma che vuoi aprirlo. Copia l'URL del feed RSS, ad esempio http://feeds.bbci.co.uk/news/world/rss.xml e NON il nome del file xml temporaneo che viene visualizzato dopo l'apertura del file in Internet Explorer. Se la pagina XML si apre in Internet Explorer dovrai tornare alla schermata precedente di Internet Explorer per copiare l'URL che ti serve dalla barra degli indirizzi. Dopo averlo copiato torna all'app Simple Blog Reader e incollalo nel blocco di testo Add Feed, quindi tocca il pulsante "Add Feed". Il feed completamente inizializzato comparirà molto rapidamente nella pagina principale. Esercizio per il lettore: implementa un contratto Condivisione o altri strumenti per semplificare l'aggiunta di nuovi URL a SimpleBlogReader. Buona lettura!

Passaggi successivi

In questa esercitazione abbiamo appreso come usare i modelli di pagina predefiniti di Microsoft Visual Studio Express 2012 per Windows 8 per creare un'app con più pagine e come spostarsi e passare i dati tra le pagine. Abbiamo imparato a usare gli stili e i modelli per conferire alla nostra app uno stile compatibile con le caratteristiche del sito Web dei blog del team di Windows. Abbiamo anche scoperto come si usano le animazioni del tema e una barra dell'app per creare un'app che rispecchi la personalità di un'app di Windows Store. Infine, abbiamo imparato ad adattare l'app a layout e orientamenti diversi in modo che abbia sempre un aspetto ottimale.

La nostra app è quasi pronta per l'invio a Windows Store. Per altre informazioni su come inviare un'app a Windows Store, vedi:

Argomenti correlati

Roadmap per app di Windows Runtime in C++