Windows Phone

幕後花絮:Windows Phone 摘要讀取程式應用程式

Matt Stroshane

下載代碼示例

我是一個飼料的愛好者。我愛神奇的 RSS 和 Atom 供稿,傳來我而不是周圍的其他方法。但允許方便地訪問如此多的資訊,在有意義的方式消耗它已成為一項挑戰。所以當我學到了一些微軟實習生正在發展一個 Windows Phone 飼料閱讀器應用程式,我很興奮地找出他們如何處理這個問題。

作為他們的實習的一部分,法蘭西斯科阿吉萊拉、 蘇曼馬拉和 Ayomikun (George) Okeowo 了 12 周來開發一個 Windows Phone 應用程式,包括一些新的 Windows Phone SDK 7.1 功能。作為新的 Windows Phone 開發,他們好的測試科目為我們的平臺、 工具和文檔。

他們考慮他們的選項後, 決定一個飼料閱讀器應用程式,將會展示一個本機資料庫、 活著的瓷磚和背景代理。他們表現出了很多更多 !在本文中,我要你度過他們如何使用這些功能。所以安裝 Windows Phone SDK 7.1、 下載的代碼和起床您的螢幕上。讓我們走 !

使用應用程式

一個中心點,這款應用程式是主頁面,MainPage.xaml (圖 1)。它包括四個全景面板:"什麼是新的""特色,""所有"和"設置"。"什麼是新"面板顯示飼料到最新的更新。六篇文章認為你想要的"特色"顯示基於您閱讀的歷史記錄。"所有人"面板列出了您的所有類別和飼料。若要下載只能通過 Wi-fi 的文章,請使用"設置"面板上的設置。

The Main Page of the App After Creating a Windows Phone News Category
圖 1 創建 Windows Phone 後 App 的主網頁新聞類別

"什麼是新"和"特色"面板提供直接到一篇文章中導航的方法。"所有人"面板提供類別和飼料的清單。從"所有人"面板中,您可以導航到按飼料或類別分組的專案的集合。此外可以使用"全部"面板上的應用程式欄中添加新的訂閱源或類別。圖 2 顯示主頁面如何關乎 app 的其他八個頁面。

The Page Navigation Map, with Auxiliary Pages in Gray
圖 2 頁導航地圖,用灰色輔助頁

類似于旋轉,您可以導航水準類別、 飼料或文章頁面上。當你在其中一個網頁上時,在應用程式欄中會出現箭頭 (請參見圖 3)。箭頭允許您顯示資料庫中的資料的上或下一個類別、 飼料或文章。例如,如果您正在查看類別頁面上的商務類別,點擊"下一步"箭頭類別頁上顯示的娛樂類別。

The Category, Feed and Article Pages with Their Application Bars Expanded
圖 3 類別,飼料和擴大其應用程式欄的文章頁面

然而,方向鍵其實別導航到另一個類別頁。相反,在同一頁綁定到不同的資料來源。攻絲手機的後退按鈕返回到"所有人"面板,而不需要任何特殊導航代碼。

從專案頁中,可以導航到共用頁和發送通過郵件、 電子郵件或一個社交網路連結。應用程式欄還提供在互聯網瀏覽器中查看文章、"添加到我的最愛"或從資料庫中刪除它的能力。

引擎蓋下

在 Visual Studio 中打開解決方案時,您將看到它是一個 C# 程式分為三個專案:

  1. FeedCast:使用者可以看到的部分 — — 前臺應用程式 (視圖和 ViewModel 的代碼)。
  2. FeedCastAgent:背景代理代碼 (定期計畫的任務)。
  3. FeedCastLibrary:建立網路和資料的共用程式碼。

這個小組使用 Silverlight 的 Windows Phone 工具組 (2011 年 11 月) 和微軟 Silverlight 4 SDK。從 app 的大多數頁中使用的 toolkit—Microsoft.Phone.Controls.Toolkit.dll—are 的控制項。例如,HubTile 控制項用於在主頁面的"特色"面板中顯示的文章。為了説明網路,這個小組使用 System.ServiceModel.Syndication.dll 從 Silverlight 4 SDK。此程式集不包含在 Windows Phone SDK 和並不專門針對手機,進行了優化,但團隊成員發現它非常適合他們的需要。

前臺應用程式專案,FeedCast,是最大的三個解決方案中。再次,這是使用者看到的應用程式的一部分。它分為九個資料夾:

  1. 轉換器:資料與使用者介面之間差距的值轉換器。
  2. 圖示:應用程式欄所使用的圖示。
  3. 圖像:由 HubTiles 在文章有沒有圖像時使用的圖像。
  4. 庫:工具組和聯合的程式集。
  5. 型號:不是代理使用的背景與資料相關的代碼。
  6. 資源:中英文和西班牙文的當地語系化資源檔。
  7. 主題:HeaderedListBox 控制項的自訂設置。
  8. ViewModels:ViewModels 和其他説明器類。
  9. 視圖:在前臺應用程式中的每個頁面的代碼。

這個應用程式遵循的模型-視圖-ViewModel (MVVM) 模式。在視圖資料夾中的代碼主要是側重于使用者介面。由 ViewModels 資料夾中的代碼定義的邏輯和資料與單個頁相關聯。雖然模型資料夾中包含一些與資料相關的代碼,在 FeedCastLibrary 專案中定義的資料物件。那裡的"模型"代碼由前臺應用程式和後臺代理重複使用。有關 MVVM 的更多資訊,請參見 wpdev.ms/mvvmpnp

FeedCastLibrary 專案中包含的資料和網路前臺應用程式和後臺代理所使用的代碼。此專案包含兩個資料夾:資料和網路。在資料檔案夾中,FeedCast"模型"是由四個檔中的分部類描述:LocalDatabaseDataCoNtext.cs、 Article.cs、 Category.cs 和 Feed.cs。DataUtils.cs 檔包含的代碼,可以執行常見的資料庫操作。使用獨立的存儲設置的説明器類是在 Settings.cs 檔中。FeedCastLibrary 專案的網路資料夾包含用來下載和分析內容從 Web 獲得,最重要的是在 WebTools.cs 檔中的下載方法的代碼。

在 FeedCastAgent 專案中,有只一班計畫­Agent.cs,這是背景代理代碼。當它運行並下載完成時調用 SendToDatabase 方法時,才會調用 OnInvoke 方法。我會詳細討論有關以後下載。

本機資料庫

最大的產能,每個小組成員專注于應用程式的不同區域。阿吉萊拉的重點使用者介面、 視圖和 ViewModels 在前臺應用程式中。Okeowo 工作網路化和獲取資料來源。馬拉工作的資料庫體系結構和操作。

在 Windows Phone 中,您可以存儲您在本機資料庫中的資料。本地部分是因為它是駐留在獨立存儲 (您的應用程式的設備上存儲桶,從其他應用程式隔離) 中的資料庫檔案。本質上,你描述您的資料庫表為平原老 CLR 物件,這些物件,該物件表示資料庫列的屬性。這將允許每個類的物件所要存儲為相應的表中的行。代表資料庫,您可以創建稱為資料上下文從 System.Data.Linq.DataCoNtext 繼承的特殊物件。

本機資料庫的神奇成分是 LINQ to SQL 運行庫 — — 你的資料管家。您調用 CreateDatabase 方法,資料上下文和 LINQ to SQL 在獨立存儲中創建將.sdf 檔。可以創建 LINQ 查詢指定的資料要然後 LINQ to SQL 返回強型別的物件,您可以將綁定到您的 UI。LINQ to SQL 允許您對您的代碼的焦點,而它將處理所有的底層資料庫操作。有關使用本機資料庫的更多資訊,請參見 wpdev.ms/localdb

而不是把所有的類類型,馬拉使用 Visual Studio 2010 最終要採取不同的方法。她以可視的方式,創建資料庫表使用伺服器資源管理器中添加連接對話方塊中創建一個 SQL Server CE 資料庫,然後新建表] 對話方塊來生成表。

一旦馬拉了她設計的架構,她用 SqlMetal.exe 來生成資料上下文。SqlMetal.exe 是從桌面 LINQ to SQL 命令列實用程式。其目的是要創建一個基於 SQL Server 資料庫的資料上下文類。它生成的代碼是非常類似于 Windows Phone 資料上下文。使用這種技術,她是能夠以可視方式生成表和快速生成資料上下文。有關 SqlMetal.exe 的更多資訊,請參見 wpdev.ms/sqlmetal

馬拉建資料庫所示圖 4。這三個主表是類別、 飼料和文章。此外,一個連結表,Category_Feed,用於啟用類別和飼料之間的多對多關係。每個類別可以關聯多個源,以及每個 feed 可以關聯多個類別。請注意應用程式的"我的最愛"功能只是一個特殊的類別,不能被刪除。

The Database Schema
圖 4 的資料庫架構

然而,由 SqlMetal.exe 生成的資料上下文仍載有一些代碼,在 Windows Phone 上不受支援。馬拉添加到 Windows Phone 專案中的資料上下文代碼檔後,她編譯要找到哪些代碼不是有效的專案。她記得不必移除一個建構函式,但其餘編譯好。

經檢驗資料上下文檔,LocalDatabase 的­DataCoNtext.cs,您可能會注意到的所有表的分部類。與這些表關聯的代碼 (即不是自動生成的 SqlMetal.exe) 的其餘部分存儲在 Article.cs、 Category.cs 和 Feed.cs 的代碼檔中。通過這種方式分隔代碼,馬拉可以更改資料庫架構而不會影響她親手寫的可擴充性方法定義。她沒有這樣做了,她將不得不重新添加的每個方法時間她自動生成 LocalDatabaseDataCoNtext.cs (因為 SqlMetal.exe 覆蓋檔中的所有代碼)。

維護併發性

與大多數 Windows Phone 旨在提供回應,流體的應用程式體驗,這一個使用多個併發執行緒完成其工作。在 UI 執行緒,而接受使用者輸入,多個後臺執行緒可能會處理下載並解析 RSS 供稿。每個這些執行緒最終將需要對資料庫進行的更改。

雖然資料庫本身提供了強健的併發訪問,DataCoNtext 類不是執行緒安全的。換句話說,在此應用程式中使用的單一全球 DataCoNtext 物件不能共用跨多個執行緒而無需添加某種形式的併發模型。要解決此問題,馬拉使用 LINQ to SQL 併發 Api 和 System.Threading 命名空間中的 mutex 物件。

在 DataUtils.cs 檔中,互斥體 WaitOne 和 ReleaseMutex 使用這些方法來同步對資料的情況下訪問其中可能有的 DataCoNtext 類之間的爭用。例如,如果調用 SaveChangesToDB 方法在差不多同一時間 (從前臺應用程式或後臺代理程式) 的多個併發執行緒,哪個代碼執行 WaitOne 第一次獲取繼續。從其他 WaitOne 調用不會完成,直至第一個代碼調用 ReleaseMutex。出於此原因,它是重要的是要把你的 ReleaseMutex 電話放在 finally 語句時使用 try/catch/最後的資料庫操作。對 ReleaseMutex 的調用,沒有其他代碼會等到 WaitOne 調用擁有的執行緒退出。從使用者的角度來看,它可以採取"永久"。

而不是一個單一的全球 DataCoNtext 物件,您也可以設計您的應用程式來創建和銷毀規模較小的 DataCoNtext 物件,每個執行緒的基礎上。然而,團隊成員發現全球 DataCoNtext 方法簡化發展。我還應注意因為這款應用程式只需防止跨執行緒訪問,不跨進程的訪問,他們可能還使用鎖而不是互斥。鎖可能提供了更好的性能。

消費資料

Okeowo 的重點將資料引入應用程式所作的努力。WebTools.cs 檔包含的代碼的大多數操作發生的地方。Webtool 類並不只是用來下載訂閱源,但 — — 它還用於新飼料頁面上搜索 bing 新的飼料。他完成這通過創建一個通用介面,IXmlFeedParser,並提取解析代碼分為不同的類別。SynFeedParser 類分析飼料和 SearchResultParser 類解析 Bing 搜尋結果。

然而,必應查詢實際上不返回 (儘管文章返回的物件由 IXmlFeedParser 的介面的集合) 的文章。相反,它返回的飼料的名稱和 Uri 的清單。是什麼給呢?好吧,Okeowo 意識到文章類已經有他描述飼料 ; 所需的屬性 他不需要創建另一個類。當解析的搜尋結果,他用於 ArticleTitle 的飼料的名稱和 ArticleBaseURI 飼料的 URI。請參閱隨附代碼下載更多 SearchResultParser.cs。

新頁 ViewModel (示例代碼中的 NewFeedPageViewModel.cs) 中的代碼顯示如何消耗的 Bing 搜尋結果。第一,GetSearchString 方法用於拼湊 Bing 搜索字串的 URI 基於 NewFeedPage,使用者輸入的搜尋條件,如下面的代碼片斷所示:

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

_NumOfResults 值限制返回多少搜尋結果。 有關通過 RSS 訪問必應的更多資訊,參閱 MSDN 庫頁,"訪問必應通過 RSS,"在 bit.ly/kc5uYO

GetSearchString 方法稱為 setresults,在那裡其實從 Bing 檢索資料 (請參見圖 5)。 Setresults 看起來有點落後,因為它會列出 lambda 運算式處理 AllDownloadsFinished 事件的"內聯,"前要開始下載的代碼實際上調用。 下載方法調用時,Webtool 物件查詢 Bing 每建成與 GetSearchString 的 URI。

圖 5 中 NewFeedPageView Setresults­Model.cs 查詢必應的新的飼料

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);
}

Webtool 下載方法也利用背景代理 (請參閱圖 6),但以不同的方式。 而不只是一個飼料從下載,代理傳遞給該方法的幾種飼料的清單。 檢索結果,代理會採取不同的策略。 而不是等待,直到所有訂閱源的文章被下載 (通過 AllDownloadsFinished 事件),儘快完成 (通過 SingleDownloadFinished 事件) 每個飼料的下載後,代理會保存文章。

圖 6 的背景代理啟動下載 (無調試評論)

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 { }
      });
  }
}

背景代理的工作是使你所有的供稿保持最新。 要做到這一點,它將傳遞給下載方法的所有訂閱源的清單。 背景代理僅有一小段時間運行,並時其時間已到,立即停止這一進程。 所以代理下載訂閱源,它將文章發送到一個喂一次資料庫。 通過這種方式的背景代理已保存新的文章,在停止之前的一個多高的概率。

方法是單-和多飼料下載實際上重載為相同的代碼。 下載代碼 (非同步) 啟動的每個 feed 的 HttpWebRequest。 儘快返回的第一個請求,它將調用 SingleDownloadFinished 事件處理常式。 飼料的資訊和文章然後打包成使用 SingleDownloadFinishedEventArgs 的事件。 如中所示圖 7,SendToDatabase 方法快感的 SingleDownloadFinshed 方法。 返回時,SendToDatabase 需要的事件參數中的條款,並將它們傳遞給名為 DataBaseTools 的 DataUtils 物件。

圖 7 背景代理將文章保存到資料庫 (無調試評論)

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();
  }
}

代理應完成所有下載其分配的時間內,它調用 NotifyComplete 方法來通知完成了它的作業系統。 這將允許的作業系統,這些未使用的資源配置給其他背景代理。

以下代碼一步更深層次,DataUtils 類中的 AddArticles 方法檢查,以確保這篇文章是新之前添加到資料庫。 注意在圖 8 如何互斥體再次使用,防止資料上下文的爭用。 最後,當找到這篇文章時要新,它將保存到資料庫使用 SaveChangesToDB 方法。

圖 8 將物品添加到 DataUtils.cs File 中的資料庫

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(); }
}

前臺應用程式使用類似于發現背景代理中的消費資料下載方法與技術。 請參閱附帶的代碼中的 ContentLoader.cs 檔,下載相應的代碼。

調度的背景代理

背景代理只是 — — 代理程式在後臺為前臺應用程式執行的工作。 正如您看到的較早前在圖 6圖 7,定義該工作的代碼是一個名為計畫類­代理。 它是從 Microsoft.Phone.Scheduler.ScheduledTaskAgent (它從 Microsoft.Phone.BackgroundAgent 派生的) 派生的。 雖然代理獲取大量的注意力,因為它不了繁重的任務,它永遠不會運行如果不是,計畫的任務。

計畫的任務是用來指定時間和頻率,背景代理將運行的物件。 在此應用程式中使用,計畫的任務是定期任務 (Microsoft.Phone.Scheduler.PeriodicTask)。 定期任務定期執行時間短量。 要其實等為其獲得該任務的排程和查詢,您可以使用計畫的行動服務 (ScheduledActionService)。 有關背景代理詳細資訊,請參閱 wpdev.ms/bgagent

此應用程式的計畫的任務代碼是在 BackgroundAgentTools.cs 檔中,在前臺應用程式專案中。 代碼定義的 StartPeriodicAgent 方法,在應用程式的建構函式中調用 App.xaml.cs (請參閱圖 9)。

圖 9 調度中 BackgroundAgentTools.cs (減去注釋) 的定期任務

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

安排定期任務前, StartPeriodicAgent 執行幾個檢查,因為始終有一個機會,你不能計畫,計畫的任務。 首先,計畫的任務可以被禁用的設置應用程式面板中的背景工作清單上的使用者。 也是有限制的一次可以在設備上啟用多少個任務。 它每個設備配置而異,但它可能會低至六。 如果您嘗試安排計畫的任務後已經超過該限制,或如果您的應用程式運行在 256 MB 的設備,或者您已經已經安排相同的任務,Add 方法將引發異常。

這個應用程式調用 StartPeriodicTask 方法,每次它發射因為背景代理商 14 天后過期。 刷新上每一次發射的代理可以確保代理可以繼續運行即使這款應用程式並不幾天內再次啟動。

中的 periodicTaskName 變數圖 9,用於查找現有的任務,等於"FeedCastAgent"。請注意此名稱不確定相應的背景代理代碼。 它是只是一個友好的名稱,您可以使用 ScheduledActionService 的工作。 前臺應用程式已經知道背景代理代碼,因為它作為對前臺應用程式專案的引用添加。 因為背景代理代碼被創建為 Windows Phone 預定任務代理類型的專案,這些工具能夠線向上的事情正確增加參考時。 您可以看到前臺應用程式背景代理指定的關係在前臺應用程式清單 (示例代碼中的 WMAppManifest.xml),如下所示:

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

並排

阿吉萊拉工作的使用者介面、 意見和 ViewModels。 他還努力的當地語系化和瓷磚功能。 瓷磚,有時也被稱為活的拼貼,從開始到 app 顯示動態內容和連結。 (無任何開發人員的工作),可以開始固定瓷磚的任何應用程式中的應用。 但是,如果您想要連結到某個地方除了您的應用程式的主頁面,您需要實現二次瓷磚。 這些功能允許您導航到您的應用程式的更深層次的使用者 — — 超越主頁面 — — 的頁,您可以自訂輔助瓦表示不管。

在 FeedCast,使用者可以啟動釘飼料或類別 (二次瓦)。 與一次點擊,他們可以立即將閱讀相關的飼料或類別的最新文章。 若要啟用這一經驗,首先他們需要能夠銷的飼料或類別的開始。 阿吉萊拉用 Windows Phone 文本功能表 Silverlight 工具組來協助這。 點選並按住主頁面的"全部"面板中的一個飼料或類別使出現的內容功能表。 從那裡,使用者可以選擇刪除或針的飼料或類別的開始。 圖 10演示端到端過程,從使用者的角度來看。


圖 10 固定到開始 Windows Phone 新聞類別和發射類別頁面

圖 11 顯示 XAML,使內容功能表。 第二個功能表項目顯示"針開始"(當英語時的顯示語言)。 該專案是拍了拍,click 事件調用 OnCategoryPinned 方法來啟動釘住。 因為這個應用程式進行當地語系化,內容功能表中的文本實際上是來自一個資源檔。 這就是為什麼標頭值綁定到 LocalizedResources.CoNtextMenuPinToStartText。

圖 11 內容功能表中移除或針腳來開始一個類別

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

此應用程式已只有兩個資源檔,一個用於西班牙文,另一個用於英語 (預設值)。 不過,因為當地語系化是在的地方,它會相對容易地添加更多的語言。 圖 12 顯示預設的資源檔,AppResources.resx。 有關詳細資訊,請參閱 wpdev.ms/globalized

The Default Resource File, AppResources.resx, Supplies the UI Text for All Languages Except Spanish
圖 12 預設資源檔,AppResources.resx,除西班牙之外的所有語言提供使用者介面文本

最初,團隊不知道它怎麼來確定究竟哪些類別或飼料需要固定。 然後阿吉萊拉發現 XAML 標記屬性 (請參見圖 11)。 團隊成員想出了,他們可能會將其綁定到該類別或飼料在 ViewModel 中的物件,然後檢索單個物件,後來,以程式設計方式。 在主頁面,類別清單綁定到一個 MainPageAllCategoriesViewModel 物件。 當調用 OnCategoryPinned 方法時,它使用 GetTagAs 方法來獲取對應于清單中的特定項的類別物件 (綁定到該標記) 如下所示:

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

GetTagAs 方法是獲取已綁定到一個容器的標記屬性的任何物件的泛型方法。 雖然這是有效的它不實際必要 MainPage.xaml.cs 及其用途的大部分。 清單中的專案已被綁定到該物件,因此將它們綁定到該標籤是有些多餘)。 而不是使用標籤,您可以使用寄件者物件的 DataCoNtext。 例如, 圖 13 顯示 OnCategoryPinned 會怎樣看使用建議的 DataCoNtext 方法。

圖 13 使用 DataCoNtext,而不 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);
      }
    }
  }
}

此 DataCoNtext 方法適用于所有情況下對 MainPage.xaml.cs 除了之一的 OnHubTileTapped 方法。 當您點擊"特色"面板中的主頁面的特色文章,這將觸發。 面臨的挑戰是由於,寄件者不綁定到一個文章類 — — 它綁定到 MainPageFeaturedViewModel。 ViewModel 包含六篇文章,所以它已經不清楚知道從 DataCoNtext 哪一個被竊聽。 使用標籤屬性中,在這種情況下,變得非常簡單綁定到相應的文章。

因為你可以銷飼料和類別的開始,AddLiveTile 方法有兩種重載。 物件和輔助瓷磚足夠不同的團隊決定不將合併到單個泛型方法的功能。 圖 14 顯示的 AddLiveTile 方法的類版本。

圖 14 寄到開始的類別物件

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();
  }
}

添加類別瓷磚之前, 的 AddLiveTile 方法使用 ShellTile 類從的活躍的拼貼,以確定是否已添加該類別的所有導航的 Uri 來都看。 如果沒有,它繼續並獲取要關聯到新的影像填滿 URL。 每當您創建新的平鋪,背景圖像需要來自本地資源。 在這種情況下,它可以使用 ImageGrabber 類來得到一個隨機分配本地影像檔。 但是,創建平鋪後,您可以更新的背景圖像遠端 URL。 但這個特定的應用程式不會這樣做。

所有的資訊,您需要指定要創建新的平鋪載在 StandardTileData 類中。 該類用於文本、 數位和背景圖像放置的瓷磚。 當您創建具有創建方法的拼貼時,StandardTileData 作為一個參數傳遞。 另一重要參數傳遞的是瓷磚導航的 URI。 這是用來在您的應用程式中的一個有意義的地方帶給使用者的 URI。

在此應用程式,從拼貼的 URI 的導航只需在應用程式的使用者。 要走遠一點,基本的 UriMapper 類使用使用者路由到正確的頁面。 App.xaml 導航元素指定所有的應用程式的 URI 映射。 如果在 UriMapping 的每個元素,由 Uri 屬性指定的值的傳入的 URI。 MappedUri 屬性指定的值將會被使用者導航到。 要保持特定的類別、 飼料或文章,括弧中的 id 值的上下文 {id} 結轉從傳入的 URI 映射的 uri,像這樣:

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

你可能會有其他的理由,使用 URI 映射 — — 例如搜索可擴充性,例如 — — 但它並不需要使用輔助的拼貼。在此應用程式,它是樣式決定使用 URI 映射。該小組認為,較短的 Uri 是更優雅和更便於使用。或者,這次要的瓷磚可以指定相同的效果 (如 MappedUri 值) 的特定于頁的 URI。

無論手段,從輔助瓷磚 URI 映射到相應的頁面之後, 使用者到達他的文章的清單的類別頁上。任務完成了。瓷磚的更多資訊,請參見 wpdev.ms/secondarytiles

但是,等一下,還有更多 !

有到此應用程式的更多比我這裡介紹的內容。一定要看一看此代碼以瞭解更多關於該小組如何處理這些和其他問題。例如,SynFeedParser.cs 有一個好的方法清理有時充斥的 HTML 標籤的源中的資料。

只是要記住這是實習醫生工作在 12 個星期,減去小清理結束時的快照。專業開發人員可能更願意以不同的方式編寫代碼的某些部分。不過,我覺得這款應用程式集成一個本機資料庫、 背景代理和瓷磚的工作非常出色。我希望您喜歡這看"幕後。"編碼愉快 !

Matt Stroshane  寫入 Windows Phone 團隊開發者文檔。他的其它貢獻 MSDN 庫功能產品,如 SQL Server,SQL Azure 和 Visual Studio。當他不能寫時,您可能會發現他的西雅圖,大街上訓練他下一步的馬拉松比賽。跟隨他在 Twitter 上 twitter.com/mattstroshane

由於下面的技術專家,檢討這篇文章:Francisco AguileraThomas FennelJohn GallardoSean McKennaSuman MalaniAyomikun (George) OkeowoHimadri Sarkar