Il presente articolo è stato tradotto automaticamente.

Windows Phone

Dietro le quinte: un'app di lettura dei feed per Windows Phone

Matt Stroshane

Scarica il codice di esempio

Io sono un drogato di mangime.Amo la magia di RSS e Atom feed, come notizia arriva a me, piuttosto che l'altro senso intorno.Ma con facile accesso alle informazioni così tanti, che consumano in modo significativo è diventata una sfida.Così quando ho saputo che alcuni stagisti Microsoft stavano sviluppando un feed-reader app Windows Phone, sono stato entusiasta di scoprire come hanno affrontato il problema.

Come parte del loro stage, Francisco Aguilera, Suman Milazzo e Ayomikun (George) frensi aveva 12 settimane per sviluppare un app Windows Phone che comprendeva alcune nuove caratteristiche Windows Phone SDK 7.1.Essendo nuovo di sviluppo Windows Phone, erano soggetti di buona prova per la nostra piattaforma, strumenti e documentazione.

Dopo aver considerato le loro opzioni, hanno deciso su un feed-reader app che dimostrerebbe un database locale, Live Tiles e un agente di sfondo.Hanno dimostrato molto di più!In questo articolo, vado a piedi attraverso come hanno usato quelle caratteristiche.Quindi, installare il Windows Phone SDK 7.1, scaricare il codice e get up sul vostro schermo.Let's get going!

Usando l'App

L'hub centrale della app è il file MainPage. xaml nella pagina principale (Figura 1).Esso è costituito da quattro pannelli di panorama: "che cosa è nuovo," "in primo piano," "all" e "impostazioni". Il pannello "what's new" Mostra gli ultimi aggiornamenti per il feed.Visualizza "Consigliati" sei articoli che pensa si vorrebbe basato sulla vostra storia di lettura.Il pannello "tutti" sono elencate tutte le categorie e i feed.Per scaricare gli articoli solo tramite Wi-Fi, utilizzare l'impostazione nel pannello "impostazioni".

The Main Page of the App After Creating a Windows Phone News CategoryFigura 1 la pagina principale dell'App dopo la creazione di un Windows Phone categoria News

"What's new" e "consigliati" pannelli forniscono un modo per passare direttamente in un articolo.Il pannello "tutti" fornisce un elenco di categorie e feed.Dal pannello "tutti", è possibile passare a un insieme di articoli che sono raggruppati per categoria o mangimi.È anche possibile utilizzare la barra delle applicazioni sul pannello "tutte" per aggiungere un nuovo feed o categoria.Figura 2 Mostra come pagina principale si riferisce alle altre otto pagine di app.

The Page Navigation Map, with Auxiliary Pages in GrayFigura 2 la mappa di navigazione di pagina, con pagine ausiliarie in grigio

Simile al basculante, è possibile spostarsi orizzontalmente nelle pagine di categoria, Feed o articolo.Quando sei in una di queste pagine, le frecce vengono visualizzati nella barra dell'applicazione (vedere Figura 3).Le frecce consentono di visualizzare i dati per la precedente o successiva categoria, feed o articolo nel database.Ad esempio, se stai visualizzando la categoria Business sulla pagina di categoria, toccando la freccia "avanti" Visualizza la categoria intrattenimento sulla pagina di categoria.

The Category, Feed and Article Pages with Their Application Bars ExpandedFigura 3 la categoria, mangimi e pagine di articoli con le loro barre di applicazione ampliato

Tuttavia, i tasti freccia non effettivamente passare a un'altra pagina di categoria.Invece, nella stessa pagina è associata a un'origine dati diversa.Toccando il pulsante indietro del telefono restituisce al pannello "tutti" senza bisogno di alcun codice di navigazione speciale.

Dalla pagina dell'articolo, potete passare alla pagina di condivisione e di inviare un link tramite messaggistica, posta elettronica o un social network.La barra delle applicazioni offre anche la possibilità di visualizzare l'articolo in Internet Explorer, "Aggiungi ai preferiti" o rimuoverlo dal database.

Sotto il cofano

Quando si apre la soluzione in Visual Studio, si vedrà che è un app di c#, divisi in tre progetti:

  1. FeedCast: La parte che l'utente vede — le app in primo piano (codice View e ViewModel).
  2. FeedCastAgent: Il codice agente background (operazione pianificata periodica).
  3. FeedCastLibrary: Il codice condiviso, networking e dati.

Il team ha utilizzato il Silverlight per Windows Phone Toolkit (novembre 2011) e il SDK di Microsoft Silverlight 4.Controlli da toolkit—Microsoft.Phone.Controls.Toolkit.dll—are usato nella maggior parte delle pagine di app.Ad esempio, HubTile controlli vengono utilizzati per visualizzare gli articoli nel pannello "vetrina" della pagina principale.Per aiutare con la rete, il team ha utilizzato System da Silverlight 4 SDK.Questo assembly non è incluso nel SDK di Windows Phone e non è specificamente ottimizzato per le applicazioni del telefono, ma i membri del team trovano che ha funzionato bene per i loro bisogni.

Il progetto di app in primo piano, FeedCast, è il più grande dei tre nella soluzione.Ancora una volta, questa è la parte dell'applicazione che l'utente vede.Esso è organizzato in nove cartelle:

  1. Convertitori: Convertitori di valore che colmano il divario tra dati e interfaccia utente.
  2. Icone: Icone utilizzate per le barre di applicazione.
  3. Immagini: Immagini utilizzate da HubTiles quando gli articoli non sono immagini.
  4. Librerie: Gli assembly Toolkit e Syndication.
  5. Modelli: Dati correlati codice non utilizzato dall'agente di sfondo.
  6. Risorse: File di risorse di localizzazione in inglese e spagnolo.
  7. Temi: Personalizzazioni per il controllo di HeaderedListBox.
  8. ViewModels: ViewModels e altre classi di supporto.
  9. Visualizzazioni: Codice per ogni pagina dell'app in primo piano.

Questo app segue il pattern Model-View-ViewModel (MVVM).Codice nella cartella Views si concentra principalmente sull'interfaccia utente.La logica e i dati associati a singole pagine è definito dal codice nella cartella ViewModel.Anche se la cartella di modelli contiene codice relative ai dati, gli oggetti di dati sono definiti nel progetto FeedCastLibrary.Il "Modello" codice c'è riutilizzato dall'agente di app e sfondo in primo piano.Per ulteriori informazioni su MVVM, vedere wpdev.ms/mvvmpnp.

Il progetto FeedCastLibrary contiene i dati e il codice networking utilizzato dall'app in primo piano e l'agente di sfondo.Questo progetto contiene due cartelle: Dati e Networking.Nella cartella dati, FeedCast "Modello" è descritta da classi parziali in quattro file: LocalDatabaseDataContext.cs, Article.cs, Category.cs e Feed.cs.Il file DataUtils.cs contiene il codice che esegue operazioni di database comuni.Una classe di supporto per l'utilizzo di impostazioni di archiviazione isolata è il file Settings XML.La cartella di rete del progetto FeedCastLibrary contiene il codice utilizzato per scaricare e analizzare i contenuti dal Web, il più significativo dei quali è i metodi di Download nel file WebTools.cs.

C'è una sola classe nel progetto FeedCastAgent, pianificato­Agent.cs, che è il codice agente di sfondo.Il metodo OnInvoke viene chiamato durante l'esecuzione e il metodo SendToDatabase viene chiamato quando il download completo.Mi occuperò di più su come scaricare più tardi.

Database locale

Per la massima produttività, ciascuno dei membri del team focalizzato su un'area diversa dell'app.Aguilera incentrato sull'interfaccia utente, Views e ViewModels in app in primo piano.Frensi ha lavorato sulla messa in rete e ottenere dati fuori i feed.Milazzo ha lavorato sull'architettura di database e le operazioni.

In Windows Phone è possibile archiviare i dati in un database locale.La parte locale è perché è un file di database che risiede nell'archiviazione isolata (secchio sulla periferica di archiviazione dell'applicazione, isolato dalle altre applicazioni).Essenzialmente, si descrivono le tabelle di database come Plain Old CLR Objects, con le proprietà di quegli oggetti che rappresentano le colonne del database.In questo modo ogni oggetto di quella classe per essere archiviato come una riga della tabella corrispondente.Per rappresentare il database, si crea un oggetto speciale, indicato come un contesto dati che eredita da System.Data.Linq.DataContext.

L'ingrediente magico di database locale è LINQ to SQL runtime — il vostro maggiordomo di dati.Si chiama il contesto dati del metodo CreateDatabase e LINQ to SQL crea il file con estensione sdf nell'archiviazione isolata.Creare le query LINQ per specificare i dati che si desidera, e LINQ to SQL restituisce fortemente tipizzato di oggetti che è possibile associare all'interfaccia utente.LINQ to SQL consente di concentrarsi sul codice mentre gestisce tutte le operazioni di database a basso livello.Per ulteriori informazioni sull'utilizzo di un database locale, vedere wpdev.ms/localdb.

Piuttosto che tipo di backup di tutte le classi, Milazzo utilizzato Visual Studio 2010 Ultimate per adottare un approccio diverso.Ha creato le tabelle del database visivamente, utilizzando la finestra di dialogo Aggiungi connessione di Esplora Server per creare un database di SQL Server CE e quindi la finestra di dialogo nuova tabella per costruire le tabelle.

Una volta Milazzo ha avuto il suo schema progettato, ha usato SQLMetal. exe per generare un contesto dati.SQLMetal. exe è un'utilità della riga di comando da desktop LINQ to SQL.Il suo scopo è quello di creare una classe del contesto dati basata su un database di SQL Server.Il codice che genera è molto simile a un contesto di dati Windows Phone.Utilizzando questa tecnica, è stata in grado di costruire le tabelle visivamente e generare il contesto dati rapidamente.Per ulteriori informazioni su SQLMetal. exe, vedere wpdev.ms/sqlmetal.

Il database di Milazzo costruito è mostrato Figura 4.Le tre tabelle primarie sono di categoria, Feed e articolo.Inoltre, una tabella di collegamento, Category_Feed, viene utilizzata per attivare una relazione molti-a-molti tra le categorie e i feed.Ogni categoria può essere associato con più feed, e ogni feed può essere associata a più categorie.Si noti che "preferiti" funzionalità di app è solo una categoria speciale che non può essere eliminata.

The Database SchemaFigura 4 Schema del Database

Tuttavia, il contesto di dati generato da SQLMetal. exe contenuta ancora qualche codice che non è supportato su Windows Phone.Dopo Milazzo aggiunto il file di codice del contesto dati per il progetto Windows Phone, ha compilato il progetto per individuare quale codice non era valido.Lei si ricorda di dover rimuovere un costruttore, ma il resto compilato bene.

All'atto dell'ispezione del file di contesto dati, accessori­DataContext.cs, si potrebbe notare che tutte le tabelle sono classi parziali.Il resto del codice associato a queste tabelle (che non è stato generato automaticamente da SQLMetal. exe) viene memorizzato nel file di codice Article.cs, Category.cs e Feed.cs.Separando il codice in questo modo, Milazzo potrebbe apportare modifiche allo schema di database senza intaccare le definizioni di metodo di estensibilità che lei ha scritto a mano.Non lei aveva fatto questo, lei avrebbe dovuto aggiungere nuovamente i metodi ogni volta lei autogenerated LocalDatabaseDataContext.cs (perché SQLMetal. exe sovrascrive tutto il codice nel file).

Mantenimento della concorrenza

Come con la maggior parte dei Windows Phone di apps che mirano a fornire un fluido reattivo, esperienza, questo utilizza più thread simultanei a fare il suo lavoro.Oltre al thread dell'interfaccia utente, che accetta l'input dell'utente, più thread di background potrebbe avere a che fare con il download e l'analisi di RSS feed.Ognuno di questi thread alla fine sarà necessario apportare modifiche al database.

Mentre il database stesso offre accesso simultaneo robusto, classe DataContext non è thread-safe.In altre parole, l'unico oggetto DataContext globale utilizzato in questa applicazione non può essere condivisa tra più thread senza l'aggiunta di qualche forma di modello di concorrenza.Per risolvere questo problema, Milazzo utilizzato LINQ to concorrenza SQL API e un oggetto mutex dallo spazio dei nomi System. Threading.

Nel file DataUtils.cs, i mutex WaitOne e ReleaseMutex metodi sono utilizzati per sincronizzare l'accesso ai dati nei casi in cui ci potrebbe essere contesa tra le classi DataContext.Ad esempio, se più thread simultanei (dalle app in primo piano o l'agente di sfondo) per chiamare il metodo SaveChangesToDB a circa lo stesso tempo, qualsiasi codice esegue il metodo WaitOne prima ottiene continuare.La chiamata di metodo WaitOne da altro non completa fino a quando il primo codice chiama ReleaseMutex.Per questo motivo, è importante mettere la chiamata ReleaseMutex istruzione finally quando utilizzando try/catch/finally per le operazioni del database.Senza una chiamata a ReleaseMutex, l'altro codice attenderà presso la chiamata WaitOne esce il thread proprietario.Dal punto di vista dell'utente, potrebbe prendere "forever".

Piuttosto che un singolo oggetto DataContext globale, è inoltre possibile progettare l'applicazione per creare e distruggere oggetti più piccoli del DataContext su base per thread.Tuttavia, i membri del team ha trovato che l'approccio globale DataContext semplificato lo sviluppo.Vorrei anche notare che perché l'app necessaria solo per la protezione contro l'accesso di cross-thread, non cross-process, che potrebbe hanno anche utilizzato un blocco anziché il mutex.Il blocco potrebbe hanno offerto prestazioni migliori.

Utilizzo di dati

Frensi concentra i suoi sforzi sul portare dati in app.Il file WebTools.cs contiene il codice dove avviene la maggior parte dell'azione.Ma la classe WebTools non è utilizzata solo per scaricare i feed — esso è utilizzato anche sulla pagina nuovo feed per la ricerca di nuove alimentazioni su Bing.Ha compiuto questo creando un'interfaccia comune, IXmlFeedParser, e astraendo il codice di analisi in diverse classi.La classe SynFeedParser analizza il feed e la classe SearchResultParser analizza i risultati di ricerca di Bing.

Tuttavia, la query di Bing non effettivamente restituire articoli (nonostante la raccolta di oggetti articolo restituito dall'interfaccia IXmlFeedParser).Al contrario, restituisce un elenco di nomi di mangimi e URI.Ciò che dà?Beh, frensi resi conto che la classe articolo aveva già la proprietà che aveva bisogno per descrivere un feed; Egli non ha bisogno di creare un'altra classe.Durante l'analisi di risultati di ricerca, ha utilizzato ArticleTitle per il nome del feed e ArticleBaseURI per l'alimentazione URI.Nel download del codice per ulteriori informazioni, vedere SearchResultParser.cs.

Il codice nella nuova pagina ViewModel (NewFeedPageViewModel.cs nell'esempio di codice) Mostra come i risultati di ricerca di Bing sono consumati.In primo luogo, il metodo GetSearchString viene utilizzato per pezzo insieme alla stringa di ricerca Bing che URI basato su termini di ricerca che l'utente immette nel NewFeedPage, come illustrato nel seguente frammento di codice:

private string GetSearchString(string query)
{
  // Format the search string.
string search = "http://api.bing.com/rss.aspx?query=feed:" + query +
    "&source=web&web.count=" + _numOfResults.ToString() +
    "&web.filetype=feed&market=en-us";
  return search;
}

Il valore di _numOfResults limiti quanti risultati della ricerca vengono restituiti. Per ulteriori informazioni sull'accesso a Bing tramite RSS, vedere la pagina di MSDN Library, "L'accesso A Bing tramite RSS," a bit.ly/kc5uYO.

Viene chiamato il metodo GetSearchString nel metodo GetResults, dove i dati effettivamente sono Estratto da Bing (vedere Figura 5). Il metodo GetResults Guarda un po ' indietro perché elenca un'espressione lambda che gestisce l'evento AllDownloadsFinished "inline", prima che il codice per avviare il download è in realtà chiamato. Quando viene chiamato il metodo di Download, il WebTools oggetto query Bing per l'URI che è stato costruito con GetSearchString.

Figura 5 il metodo GetResults in NewFeedPageView­Model.cs una query Bing per nuovi feed

public void GetResults(string query, Action<int> Callback)
{
  // Clear the page ViewModel.
Clear();
  // Get the search string and put it into a feed.
Feed feed = new Feed { FeedBaseURI = GetSearchString(query) };
  // Lambda expression to add results to the page
  // ViewModel after the download completes.
// _feedSearch is a WebTools object.
_feedSearch.AllDownloadsFinished += (sender, e) =>
    {
      // See if the search returned any results.
if (e.Downloads.Count > 0)
      {
        // Add the search results to the page ViewModel.
foreach (Collection<Article> result in e.Downloads.Values)
        {
          if (null != result)
          {
            Deployment.Current.Dispatcher.BeginInvoke(() =>
              {
                foreach (Article a in result)
                {
                  lock (_lockObject)
                  {
                    // Add to the page ViewModel.
Add(a);
                  }
                }
                Callback(Count);
              });
          }
        }
      }
      else
      {  
        // If no search results were returned.
Deployment.Current.Dispatcher.BeginInvoke(() =>
          {
            Callback(0);
          });
      }
    };
  // Initiate the download (a Bing search).
_feedSearch.Download(feed);
}

Il metodo WebTools Download è anche utilizzato dall'agente di sfondo (vedere Figura 6), ma in modo diverso. Invece di scaricare solo un feed, l'agente passa al metodo un elenco di feed diversi. Per recuperare i risultati, l'agente assume una strategia diversa. Piuttosto che aspettare fino a quando gli articoli da tutti i feed vengono scaricati (tramite l'evento AllDownloadsFinished), l'agente salva gli articoli appena ogni download feed è completa (tramite l'evento SingleDownloadFinished).

Figura 6 l'agente sfondo avvia un Download (senza commenti di Debug)

protected override void OnInvoke(ScheduledTask task)
{
  // Run the periodic task.
List<Feed> allFeeds = DataBaseTools.GetAllFeeds();
  _remainingDownloads = allFeeds.Count;
  if (_remainingDownloads > 0)
  {
    Deployment.Current.Dispatcher.BeginInvoke(() =>
      {
        WebTools downloader = new WebTools(new SynFeedParser());
        downloader.SingleDownloadFinished += SendToDatabase;
        try
        {
          downloader.Download(allFeeds);
        }
        // TODO handle errors.
catch { }
      });
  }
}

Il lavoro dell'agente di sfondo è quello di mantenere tutti i vostri feed aggiornati. Per fare questo, passa al metodo Download un elenco di tutti i feed. L'agente di sfondo ha solo un breve lasso di tempo di esecuzione, e quando il tempo è scaduto, il processo viene arrestato immediatamente. Così come l'agente download feed, invia gli articoli nel database un feed in un momento. In questo modo l'agente di sfondo ha una molto maggiore probabilità di salvataggio nuovi articoli prima si è fermato.

Il Download singolo-feed e multi-feed sono metodi di overload effettivamente per lo stesso codice. Il codice di download avvia un HttpWebRequest per ogni feed (asincrono). Non appena la prima richiesta restituisce, chiama il gestore di eventi SingleDownloadFinished. Le informazioni feed e gli articoli sono poi confezionati nell'evento utilizzando il SingleDownloadFinishedEventArgs. Come mostrato Figura 7, il metodo SendToDatabase è cablato fino al metodo SingleDownloadFinshed. Quando che restituisce, SendToDatabase prende gli articoli fuori gli argomenti dell'evento e li passa all'oggetto DataUtils denominato DataBaseTools.

Figura 7 l'agente sfondo Salva articoli nel Database (senza commenti di Debug)

private void SendToDatabase(object sender, 
  SingleDownloadFinishedEventArgs e)
{
  // Ensure download is not null!
if (e.DownloadedArticles != null)
  {
    DataBaseTools.AddArticles(e.DownloadedArticles, e.ParentFeed);
    _remainingDownloads--;
  }
  // If no remaining downloads, tell scheduler the background agent is done.
if (_remainingDownloads <= 0)
  {
    NotifyComplete();
  }
}

Dovrebbe l'agente di finire tutti i download entro il tempo assegnato, chiama il metodo NotifyComplete per notificare il sistema operativo che è finito. Questo permette al sistema operativo di allocare le risorse inutilizzate ad altri agenti di sfondo.

In seguito il codice un passo più profondo, il metodo AddArticles della classe DataUtils controlli per assicurarsi che l'articolo è nuovo prima che si aggiunge al database. Nota Figura 8 come un mutex viene utilizzato di nuovo per evitare la contesa per il contesto dei dati. Infine, quando l'articolo è trovato per essere nuovo, viene salvato nel database con il metodo SaveChangesToDB.

Figura 8 aggiungendo gli articoli al Database nel File DataUtils.cs

public void AddArticles(ICollection<Article> newArticles, Feed feed)
{
  dbMutex.WaitOne();
  // DateTime date = SynFeedParser.latestDate;
  int downloadedArticleCount = newArticles.Count;
  int numOfNew = 0;
  // Query local database for existing articles.
for (int i = 0; i < downloadedArticleCount; i++)
  {
    Article newArticle = newArticles.ElementAt(i);
    var d = from q in db.Article
            where q.ArticleBaseURI == newArticle.ArticleBaseURI
            select q;
    List<Article> a = d.ToList();
    // Determine if any articles are already in the database.
bool alreadyInDB = (d.ToList().Count == 0);
    if (alreadyInDB)
    {
      newArticle.Read = false;
      newArticle.Favorite = false;
      numOfNew++;
    }
    else
    {
      // If so, remove them from the list.
newArticles.Remove(newArticle);
      downloadedArticleCount--;
      i--;
    }               
  }
  // Try to submit and update counts.
try
  {
    db.Article.InsertAllOnSubmit(newArticles);
    Deployment.Current.Dispatcher.BeginInvoke(() =>
      {
        feed.UnreadCount += numOfNew;
        SaveChangesToDB();
      });
    SaveChangesToDB();
  }
  // TODO handle errors.
catch { }
  finally { dbMutex.ReleaseMutex(); }
}

L'app in primo piano utilizza una tecnica simile a quella trovata nell'agente di sfondo per l'utilizzo dei dati con il metodo di Download.Vedere il file ContentLoader.cs nel codice di accompagnamento di download per il codice paragonabile.

Pianificazione dell'agente di sfondo

L'agente di sfondo è proprio questo, un agente che esegue il lavoro in background per le app in primo piano.Come si è visto in precedenza Figura 6 e Figura 7, il codice che definisce che il lavoro è una classe denominata pianificato­agente.Esso è derivato da Microsoft.Phone.Scheduler.ScheduledTaskAgent (che è derivato da Microsoft.Phone.BackgroundAgent).Mentre l'agente ottiene un sacco di attenzione perché fa il sollevamento di carichi pesanti, non funzionerebbe mai se non fosse per l'attività pianificata.

L'attività pianificata è l'oggetto utilizzato per specificare quando e quanto spesso l'agente sfondo verrà eseguito.L'attività pianificata utilizzato in questa applicazione è un'attività periodica (Microsoft.Phone.Scheduler.PeriodicTask).Un'attività periodica viene eseguita regolarmente per un breve lasso di tempo.Per ottenere realmente tale compito sulla pianificazione e la query per esso e così via, si utilizza il servizio di azione programmata (ScheduledActionService).Per ulteriori informazioni su agenti di sfondo, vedere wpdev.ms/bgagent.

Il codice di attività pianificata per questa app è nel file BackgroundAgentTools.cs, nel progetto di app in primo piano.Che il codice definisce il metodo StartPeriodicAgent, che viene chiamato da App.xaml.cs nel costruttore di applicazione (vedere Figura 9).

Figura 9 pianificazione dell'attività periodiche in BackgroundAgentTools.cs (meno commenti)

public bool StartPeriodicAgent()
{
  periodicDownload = ScheduledActionService.Find(periodicTaskName) as PeriodicTask;
  bool wasAdded = true;
  // Agents have been disabled by the user.
if (periodicDownload != null && !periodicDownload.IsEnabled)
  {
    // Can't add the agent.
Return false!
wasAdded = false;
  }
  // If the task already exists and background agents are enabled for the
  // application, then remove the agent and add again to update the scheduler.
if (periodicDownload != null && periodicDownload.IsEnabled)
  {
    ScheduledActionService.Remove(periodicTaskName);
  }
  periodicDownload = new PeriodicTask(periodicTaskName);
  periodicDownload.Description =
    "Allows FeedCast to download new articles on a regular schedule.";
  // Scheduling the agent may not be allowed because maximum number
  // of agents has been reached or the phone is a 256MB device.
try
  {
    ScheduledActionService.Add(periodicDownload);
  }
  catch (SchedulerServiceException) { }
  return wasAdded;
}

Prima di pianificare l'attività periodica, StartPeriodicAgent esegue i controlli di pochi perché c'è sempre una possibilità che non è possibile pianificare l'attività pianificata. Prima di tutto, le attività pianificate possono essere disattivate dagli utenti sull'elenco di attività di sfondo nel pannello delle impostazioni applicazioni. C'è anche un limite a quanti compiti possono essere attivate su un dispositivo alla volta. Essa varia per la configurazione del dispositivo, ma potrebbe essere basso quanto sei. Se si tenta di pianificare un'attività pianificata dopo tale limite è stato superato, o se l'applicazione è in esecuzione su un dispositivo di 256 MB, o se tu hai già pianificato lo stesso compito, il metodo Add genererà un'eccezione.

Questa applicazione chiama il metodo StartPeriodicTask ogni volta che si lancia perché gli agenti sfondo scadano dopo 14 giorni. Rinfrescante l'agente su ogni lancio assicura che l'agente può continuare l'esecuzione anche se la app non è lanciato nuovamente per diversi giorni.

La variabile periodicTaskName in Figura 9, utilizzato per trovare un'attività esistente, è uguale a "FeedCastAgent." Si noti che questo nome non identifica il corrispondente codice agente di sfondo. È semplicemente un nome descrittivo che è possibile utilizzare per lavorare con ScheduledActionService. L'app in primo piano già conosce il codice agente sfondo perché è stato aggiunto come riferimento al progetto di app in primo piano. Poiché il codice agente di sfondo è stato creato come un progetto di tipo Windows Phone pianificate attività agente, gli strumenti sono stati in grado di cablare le cose correttamente quando è stato aggiunto il riferimento. Si può vedere il rapporto agente app-sfondo in primo piano specificato in app in primo piano manifesto (WMAppManifest. xml nel codice di esempio), come illustrato di seguito:

<Tasks>
  <DefaultTask Name="_default" 
    NavigationPage="Views/MainPage.xaml" />
  <ExtendedTask Name="BackgroundTask">
    <BackgroundServiceAgent Specifier="ScheduledTaskAgent" 
      Name="FeedCastAgent"
      Source="FeedCastAgent" Type="FeedCastAgent.ScheduledAgent"/>
  </ExtendedTask>
</Tasks>

Affianca

Aguilera ha lavorato sull'interfaccia utente, le viste e ViewModel. Ha anche lavorato sulla localizzazione e funzione di piastrelle. Piastrelle, a volte indicati come Live Tiles, visualizzare contenuti dinamici e link per l'app dall'inizio. L'applicazione delle mattonelle di qualsiasi applicazione può essere appuntato all'inizio (senza alcun lavoro da parte di sviluppatore). Tuttavia, se si desidera collegare a qualche parte diversa dalla pagina principale dell'applicazione, è necessario implementare piastrelle secondario. Questi consentono di navigare l'utente più in profondità nel vostro app — di là della pagina principale — a una pagina che è possibile personalizzare per qualunque cosa rappresenta la piastrella secondario.

In FeedCast, gli utenti possono pin un feed o categoria (tessera secondaria) a Start. Con rubinetto singolo si possono immediatamente leggere gli ultimi articoli relativi a quel feed o categoria. Per abilitare questa esperienza, prima hanno bisogno per poter appuntare il feed o categoria di Start. Aguilera usato il Toolkit di Silverlight per Windows Phone ContextMenu per facilitare questo. Toccando e tenendo premuto un feed o categoria nel pannello "tutti" della pagina principale rende il menu di scelta rapida vengono visualizzati. Da lì, gli utenti possono scegliere di rimuovere o pin alimentare o alla categoria di Start. Figura 10viene illustrato il processo end-to-end dalla prospettiva dell'utente.


Figura 10 appuntare la categoria News Windows Phone Start e lanciando la pagina di categoria

Figura 11 Mostra il codice XAML che rende possibile il menu di scelta rapida. Il secondo oggetto MenuItem viene visualizzato "pin per iniziare" (quando l'inglese è la lingua di visualizzazione). Quando tale elemento è tappato, l'evento click chiama il metodo OnCategoryPinned per avviare il pinning. Perché questo app è localizzato, il testo per il menu di contesto viene in realtà da un file di risorse. Ecco perché il valore dell'intestazione è associato a LocalizedResources.ContextMenuPinToStartText.

Figura 11 il menu di scelta rapida per rimuovere o Pin per avviare una categoria

<toolkit:ContextMenuService.ContextMenu>
  <toolkit:ContextMenu>
    <toolkit:MenuItem Tag="{Binding}"
      Header="{Binding LocalizedResources.ContextMenuRemoveText,
               Source={StaticResource LocalizedStrings}}"
      IsEnabled="{Binding IsRemovable}"
      Click="OnCategoryRemoved"/>
    <toolkit:MenuItem Tag="{Binding}"
      Header="{Binding LocalizedResources.ContextMenuPinToStartText,
               Source={StaticResource LocalizedStrings}}"
      IsEnabled="{Binding IsPinned, 
      Converter={StaticResource IsPinnable}}"
      Click="OnCategoryPinned"/>
  </toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>

Questa applicazione ha risorse solo due file, uno per lo spagnolo e l'altro per l'inglese (impostazione predefinita).Tuttavia, poiché la localizzazione è a posto, sarebbe relativamente facile da aggiungere altre lingue.Figura 12 Mostra il file di risorse predefinito, AppResources.resx.Per ulteriori informazioni, vedere wpdev.ms/globalized.

The Default Resource File, AppResources.resx, Supplies the UI Text for All Languages Except SpanishFigura 12 il File di risorse predefinito, AppResources.resx, fornisce il testo dell'interfaccia utente per tutte le lingue tranne spagnolo

Inizialmente, il team non abbastanza sicuro come stava andando a determinare esattamente quale categoria o mangimi doveva essere bloccato.Poi Aguilera scoperto l'attributo Tag XAML (vedere Figura 11).I membri del team ha capito che potrebbero associarlo alla categoria o feed oggetti nel ViewModel e quindi recuperare i singoli oggetti, più tardi, a livello di codice.Sulla pagina principale, l'elenco delle categorie è associato a un oggetto MainPageAllCategoriesViewModel.Quando viene chiamato il metodo OnCategoryPinned, viene utilizzato il metodo GetTagAs per ottenere l'oggetto categoria (associato al Tag) che corrisponde con quel particolare elemento dell'elenco, in questo modo:

private void OnCategoryPinned(object sender, EventArgs e)
{
  Category tappedCategory = GetTagAs<Category>(sender);
  if (null != tappedCategory)
  {
    AddTile.AddLiveTile(tappedCategory);
  }
}

Il metodo GetTagAs è un metodo generico per l'ottenimento di qualsiasi oggetto che è stato associato all'attributo del Tag di un contenitore. Anche se questo è efficacia, non è realmente necessario per la maggior parte dei suoi impieghi su file MainPage.xaml.cs. Gli elementi dell'elenco sono già associati all'oggetto, quindi è un po' ridondante, associandole al Tag. Piuttosto che utilizzare Tag, è possibile utilizzare la proprietà DataContext dell'oggetto Sender. Ad esempio, Figura 13 illustrato come OnCategoryPinned sarebbe guardare utilizzando l'approccio consigliato DataContext.

Figura 13 un esempio di utilizzo di DataContext invece GetTagAs

private void OnCategoryPinned(object sender, EventArgs e)
{
  Category tappedCategory = null;
  if (null != sender)
  {
    FrameworkElement element = sender as FrameworkElement;
    if (null != element)
    {
      tappedCategory = element.DataContext as Category;
      if (null != tappedCategory)
      {
        AddTile.AddLiveTile(tappedCategory);
      }
    }
  }
}

Questo approccio DataContext funziona bene per tutti i casi il file MainPage.xaml.cs tranne uno, il metodo OnHubTileTapped. Questo viene generato quando si tocca un articolo consigliato nel pannello "vetrina" della pagina principale. La sfida è dovuto al fatto che il mittente non è associato a una classe articolo — è associato a MainPageFeaturedViewModel. Che ViewModel contiene sei articoli, in modo chiaramente non ha conosciuto da DataContext che uno è stato sfruttato. Utilizzando la proprietà Tag, in questo caso, rende veramente facile da associare all'articolo appropriato.

Poiché è possibile bloccare i feed e categorie all'inizio, il metodo AddLiveTile ha due overload. Oggetti e piastrelle secondaria differiscono abbastanza che il team ha deciso di non unire la funzionalità in un unico metodo generico. Figura 14 Mostra la versione di categoria del metodo AddLiveTile.

Figura 14 appuntare un oggetto categoria Start

public static void AddLiveTile(Category cat)
{
  // Does Tile already exist?
If so, don't try to create it again.
ShellTile tileToFind = ShellTile.ActiveTiles.FirstOrDefault(x => 
    x.NavigationUri.ToString().Contains("/Category/" + 
    cat.CategoryID.ToString()));
  // Create the Tile if doesn't already exist.
if (tileToFind == null)
  {
    // Create an image for the category if there isn't one.
if (cat.ImageURL == null || cat.ImageURL == String.Empty)
    {
      cat.ImageURL = ImageGrabber.GetDefaultImage();
    }
    // Create the Tile object and set some initial properties for the Tile.
StandardTileData newTileData = new StandardTileData
    {
      BackgroundImage = new Uri(cat.ImageURL, 
      UriKind.RelativeOrAbsolute),
      Title = cat.CategoryTitle,
      Count = 0,
      BackTitle = cat.CategoryTitle,
      BackContent = "Read the latest in " + cat.CategoryTitle + "!",
    };
    // Create the Tile and pin it to Start.
// This will cause a navigation to Start and a deactivation of the application.
ShellTile.Create(
      new Uri("/Category/" + cat.CategoryID, UriKind.Relative), 
      newTileData);
    cat.IsPinned = true;
    App.DataBaseUtility.SaveChangesToDB();
  }
}

Prima di aggiungere una categoria delle mattonelle, il metodo AddLiveTile utilizza la classe ShellTile per guardare la navigazione URI da tutte le tessere attive per determinare se la categoria è già stato aggiunto. In caso contrario, continua e si ottiene l'URL di un'immagine da associare con la nuova tessera. Ogni volta che si crea una nuova icona, l'immagine di sfondo deve provenire da una risorsa locale. In questo caso, utilizza la classe ImageGrabber per ottenere un file di immagine locale assegnato in modo casuale. Tuttavia, dopo aver creato una tegola, è possibile aggiornare l'immagine di sfondo con un URL remoto. Ma questa app particolare che non fa.

Tutte le informazioni è necessario specificare per creare una nuova tegola è contenuta nella classe StandardTileData. Tale classe viene utilizzata per inserire testo, numeri e lo sfondo immagini nelle piastrelle. Quando si crea la piastrella con il metodo Create, la StandardTileData viene passato come parametro. L'altro parametro importante che è passato è la navigazione Tile URI. Questo è l'URI che viene utilizzato per portare gli utenti a un posto significativo nella tua applicazione.

In questa applicazione, la navigazione URI da piastrella richiede solo l'utente per quanto riguarda l'app. Per andare oltre, una base UriMapper classe viene utilizzato per indirizzare gli utenti alla pagina di destra. L'elemento di navigazione app. XAML specifica tutti i mapping di URI per l'app. In ogni elemento di UriMapping, il valore specificato dall'attributo Uri è l'URI in arrivo. Il valore specificato dall'attributo MappedUri è dove l'utente sarà possibile spostarsi. Per mantenere il contesto della particolare categoria, feed o articolo, il valore di id in parentesi, {id}, è portato dall'URI in arrivo all'URI mappato, in questo modo:

<navigation:UriMapping Uri="/Category/{id}" MappedUri=
  "/Views/CategoryPage.xaml?id={id}"/>

Si potrebbero avere altre ragioni per utilizzare un URI mapper — ad esempio extensibility di ricerca, per esempio — ma, non è necessario utilizzare una tessera secondaria.In questa applicazione, è stata una decisione di stile di utilizzare mapping degli URI.La squadra ha ritenuto che gli URI più corti erano più elegante e facile da usare.In alternativa, le piastrelle secondario potrebbe hai specificato un URI specifico della pagina (ad esempio i valori di MappedUri) per lo stesso effetto.

Indipendentemente dal mezzo, dopo che l'URI da piastrella secondario viene mappato alla pagina appropriata, l'utente arriva sulla pagina di categoria con un elenco dei suoi articoli.Missione compiuta.Per ulteriori informazioni su piastrelle, vedere wpdev.ms/secondarytiles.

Ma aspetta, non c'è più!

C'è molto di più di questa app di ciò che coperto qui.Essere sicuri di dare un'occhiata al codice per saperne di più su come la squadra si è avvicinato questi e altri problemi.Ad esempio, SynFeedParser.cs è un bel modo di ripulire i dati da feed che a volte sono disseminati con tag HTML.

Basta tenere a mente che questa è un'istantanea di lavoro dei tirocinanti alla fine delle 12 settimane, meno una piccola pulizia.Gli sviluppatori Pro potrebbero preferire ad alcune parti di codice in modo diverso.Tuttavia, penso che l'app ha fatto un grande lavoro di integrazione di un database locale, agente in background e piastrelle.Spero che vi sia piaciuto questo sguardo "dietro"le quinte. Codificazione felice!

Matt Stroshane  scrive la documentazione sviluppatore per il team di Windows Phone.I suoi altri contributi alla libreria MSDN dispongono di prodotti quali SQL Server, SQL Azure e Visual Studio.Quando non sta scrivendo, si potrebbe trovare lui per le strade di Seattle, corsi di formazione per la sua prossima maratona.Seguirlo su Twitter a twitter.com/mattstroshane.

Grazie ai seguenti esperti tecnici per la revisione di questo articolo: Francisco Aguilera, Thomas finocchio, John Gallardo, Sean McKenna, Suman Milazzo, Ayomikun (George) frensi e Deni Sarkar