本文章是由機器翻譯。

位置感知程式設計

將 Windows Phone 7 上的 Bing 路線視覺化

Sandrino Di Di

下載程式碼範例

Microsoft Windows 電話 7 隨附方便使用 geolocation 的 API,可讓您決定目前的位置和一位使用者,以經度和緯度 (,有時也高度) 的移動。一旦您有存取這些資料,您就可以準備 Windows 電話 7 應用程式中建立能夠識別位置的功能。

如果您要建立漢堡餐廳的應用程式,它是很棒如果 — 除了呈現的功能表和促銷活動,您也可以找出最接近的餐廳根據使用者的目前位置。另一種很棒的功能是能夠找出您附近的人員。請考慮案例的銷售人員想要查看是否他們可以造訪他們的用戶端中間會議。

這份文件會著重於如何將此資料帶給 Windows 電話 7 應用程式,以及如何以視覺化方式檢視路由和位置以不同的方式。來自 Bing 對應 API 的實際資料。因此我可以告訴您一些進階的概念之前,務必看一下 Bing 對應 API 的基本概念。

Bing 對應 API 的簡介

您需要的第一件事是將使用中的 Bing 帳戶。在 Bing 對應帳戶中心 (bingmapsportal.com),您需要使用 Windows Live ID 登入,以 Bing 對應的帳號。建立您的帳戶之後, 您必須存取您帳戶的詳細資訊,或者,可以在建立索引鍵。因為您將要建置應用程式的 Windows 電話 7,就是您可以撰寫的程式碼類似 http://localhost 的應用程式 URL。

除了建立帳戶,這個頁面也允許您監視 Bing 對應 API 的使用方式。您應該決定要在實際執行應用程式中使用 Bing 對應,您也必須要回到這個頁面,請聯絡人有關授權。

Bing 對應 API 實際提供幾個服務,以及 Windows 電話 7 應用程式將使用 SOAP 服務。我將會執行下列服務的簡短概觀:

  • Geocodedev.virtualearth。net/webservices/v1/geocodeservice/geocodeservice.svc
  • 足以dev.virtualearth。net/webservices/v1/imageryservice/imageryservice.svc
  • 路由dev.virtualearth。net/webservices/v1/routeservice/routeservice.svc
  • 搜尋dev.virtualearth。net/webservices/v1/searchservice/searchservice.svc

Geocode 服務可讓您使用經緯度和地址; 足以服務可讓您使用的實際映像 (航空、 bird's-eye 和道路); 路由服務將會幫助您計算兩個或多個點; 之間路由 和搜尋服務可讓您尋找為主仰賴手動輸入 (例如"餐廳中 「 布魯塞爾 」) 」 的位置。

若要讓使用這些服務,您只需要加入對"它們的服務參考 」 的上一個 Url。請注意這個動作會建立或更新的 ServiceReferences.ClientConfig 檔案。圖 1示範如何叫用 Geocode 服務的 Geocode 方法。

圖 1叫用 Geocode 方法的 Geocode 服務

// Create the request.
var geoRequest = new GeocodeRequest(); 
geoRequest.Credentials = new Credentials(); 
geoRequest.Credentials.ApplicationId = "<my API key>"; 
geoRequest.Address = new Address(); 
geoRequest.Address.CountryRegion = "Belgium"; 
geoRequest.Address.PostalTown = "Brussels"; 
             
// Execute the request and display the results.
var geoClient = new GeocodeServiceClient("BasicHttpBinding_IGeocodeService"); 
geoClient.GeocodeAsync(geoRequest); 
geoClient.GeocodeCompleted += (s, e) => 
{
  if (e.Result != null && e.Result.Results.Any(o => 
    o.Locations != null && o.Locations.Any()))
    Location = e.Result.Results.FirstOrDefault().Locations.FirstOrDefault();
  else if (e.Error != null)
    Error = e.Error.Message;
  else
    Error = "No results or locations found."; 
};

每個服務方法根據要求/回應。 您建立您準備伺服器的問題,並設定 API 金鑰的要求物件。 在這種情況下,創建的 GeocodeRequest,並將會問我提供的"布魯塞爾,比利時"GeocodeLocation 伺服器後創建的請求,我只是以非同步方式調用用戶端。 最後,您一定會收到回應,可以讓您的資訊和 — 和的問題,也會發現錯誤。 執行範例應用程式所附下載中檢視 GeocodeServiceClient 的實際運作以及了解如何地點 (或錯誤) 來顯示螢幕上使用資料繫結。

應用程式將使用 Geocode 及路由服務來計算兩個地址之間的路徑,並顯示給使用者。

計算路線

使用 Bing 路由服務,您可以計算 a 點之間的路由到點 b。 如同上一個範例中,這是與要求/回應。 您必須先找到每一個地址實際 geolocation (使用 GeocodeRequest),而且使用這些位置您也可以建立 RouteRequest。 範例應用程式所附下載中的包含所有的程式碼,但圖 2會顯示一個簡短的範例,這如何達成。

圖 2建立 RouteRequest

// Create the request.
var routeRequest = new RouteRequest(); 
routeRequest.Credentials = new Credentials(); 
routeRequest.Credentials.ApplicationId = "<my API key>"; 
routeRequest.Waypoints = new ObservableCollection<Waypoint>(); 
routeRequest.Waypoints.Add(fromWaypoint); 
routeRequest.Waypoints.Add(toWaypoint); 
routeRequest.Options = new RouteOptions(); 
routeRequest.Options.RoutePathType = RoutePathType.Points; 
routeRequest.UserProfile = new UserProfile(); 
routeRequest.UserProfile.DistanceUnit = DistanceUnit.Kilometer; 
                 
// Execute the request.
var routeClient = new RouteServiceClient("BasicHttpBinding_IRouteService"); 
routeClient.CalculateRouteCompleted += 
  new EventHandler<CalculateRouteCompletedEventArgs>(OnRouteComplete); 
routeClient.CalculateRouteAsync(routeRequest);

請注意要求的個導航點屬性集合,可讓您加入多個導航點。這可能是有趣時您需要知道的路由而不是完整路線只是從 a 點到點 b。

當您執行 CalculateRouteAsync 方法時,將會啟動此服務是困難的工作:計算路線。 列出所有的 itinerary 項目 (例如關閉,如此類推採取逃生口動作)。 計算工期和距離; 正在列出所有資料點 (geolocations) 等等。圖 3顯示出現在 RouteResponse 中一些重要資料的概觀。

圖 3RouteResponse 內容

在地圖上顯示路由

在第一個範例中,我會使用 RoutePath 點顯示在地圖上的路由。因為 Windows 電話 7 工具組已經包含 Bing 對應控制項,您只需要將參考加入至組件 Microsoft.Phone.Controls.Maps。接下來很容易在電話上顯示對應。以下是如何顯示顯示 「 布魯塞爾 」 (CredentialsProvider,才能設定 API 索引鍵) 的對應的範例:

<maps:Map Center="50.851041,4.361572" ZoomLevel="10" 
  CredentialsProvider="{StaticResource MapCredentials}" />

如果您想要使用的任何控制項對應組件中,我建議您將加入之前加入 Bing 服務的服務參考這個組件的參考。 服務參考要再重複型別,例如 Microsoft.Phone.Controls.Maps.Platform.Location,而非建立新的型別,而不需要撰寫轉換方法或使用服務所傳回的資料部份的值轉換子。

所以現在您知道如何計算路由之間的兩個點,以及如何在電話上顯示地圖。 讓我們將這兩種技術一起以視覺化方式檢視在地圖上的路由。 RoutePath 中的點將用來繪製在地圖上。 地圖控制項可讓您新增圖形] 圖釘,例如顯示開始和結束的路由,) 和 MapPolyline (若要繪製 GeoCoordinates 為基礎的路由)。

因為服務傳回的點並非的相同的型別對應控制項所使用的點,我建立了兩個小型的擴充方法,將點轉換為正確的型別,以顯示圖 4

圖 4若要更正型別轉換點延伸方法

public static GeoCoordinate ToCoordinate(this Location routeLocation) 
{ 
  return new GeoCoordinate(routeLocation.Latitude, routeLocation.Longitude); 
} 
  
public static LocationCollection ToCoordinates(this IEnumerable<Location> points)
{ 
  var locations = new LocationCollection(); 
  
  if (points != null) 
  { 
    foreach (var point in points) 
    { 
      locations.Add(point.ToCoordinate()); 
    } 
  } 
  
  return locations; 
}

CalculateRoute 方法完成時,您可以使用這些擴充方法在 RouteResponse 上。 在轉換之後,這些擴充方法會傳回,比方說,可用來繫結到 Bing 對應控制項的型別。 因為這是將 Silverlight 應用程式,我們應該使用 IValueConverter 來執行實際的轉換。 圖 5範例會將位置轉換成 GeoCoordinates 的值轉換子。

圖 5使用 IValueConverter

public class LocationConverter : IValueConverter 
{ 
  public object Convert(object value, Type targetType,  
    object parameter, CultureInfo culture) 
  { 
    if (value is Location) 
    { 
      return (value as Location).ToCoordinate(); 
    } 
    else if (value is IEnumerable<Location>) 
    { 
      return (value as IEnumerable<Location>).ToCoordinates(); 
    } 
    else 
    { 
      return null; 
    } 
  }
}

現在是時候來設定資料繫結。 資料繫結會使用轉換程式,所以務必要宣告的第一個。 這些可以宣告中的網頁資源或應用程式資源 (如果您計劃重複使用轉換程式),如下所示:

<phone:PhoneApplicationPage.Resources>
  <converters:LocationConverter x:Key="locationConverter" />
  <converters:ItineraryItemDisplayConverter x:Key="itineraryConverter" />
</phone:PhoneApplicationPage.Resources>

之後宣告轉換子可以新增對應控制項和其他覆疊] 控制項 (就像 MapPolyline 和大頭針),並將它們繫結至所需的屬性,如下所示:

<maps:Map Center="50.851041,4.361572" ZoomLevel="10" 
  CredentialsProvider="{StaticResource MapCredentials}">
  <maps:MapPolyline Locations="{Binding RoutePoints, 
    Converter={StaticResource locationConverter}}" 
    Stroke="#FF0000FF" StrokeThickness="5" />
  <maps:Pushpin Location="{Binding StartPoint, 
    Converter={StaticResource locationConverter}}" Content="Start" />
  <maps:Pushpin Location="{Binding EndPoint, 
    Converter={StaticResource locationConverter}}" Content="End" />
</maps:Map>

如您所見,這些繫結會使用先前已宣告為將資料轉換成了解對應控制項的格式轉換工具。 最後,您必須設定這些屬性,CalculateMethod 後,如下所示:

private void OnRouteComplete(object sender, CalculateRouteCompletedEventArgs e)
{
  if (e.Result != null && e.Result.Result != null 
    && e.Result.Result.Legs != null & e.Result.Result.Legs.Any())
  {
    var result = e.Result.Result;
    var legs = result.Legs.FirstOrDefault();
 
    StartPoint = legs.ActualStart;
    EndPoint = legs.ActualEnd;
    RoutePoints = result.RoutePath.Points;
    Itinerary = legs.Itinerary;
  }
}

圖 6顯示螢幕之後啟動應用程式和計算路線。

圖 6路由的圖形化表示

顯示方向

如您所見,在地圖上顯示路由是相當標準的項目。在下一個範例中,我將示範如何建立自訂控制項,會顯示從開始到結束使用文字和每個 ItineraryItem 的摘要說明。圖 7會顯示最後的結果。

圖 7顯示開始到結束的說明

圖 1,您會看到腳是其中一個 RouteResult 屬性。腳屬性包含一或多個 「 腿部 」 物件,其中每一個包含一系列 ItineraryItems。使用 ItineraryItems,它將可能會填滿的控制項,您可以看到在圖 7。中的每一行圖 7顯示的總秒數與目前步驟的索引,該步驟中,佔全部距離 ItineraryItem。ItineraryItem 並不會持續追蹤的目前逐步執行計數,所以我建立稱為 ItineraryItemDisplay 的小型類別:

public class ItineraryItemDisplay  
{
  public int Index { get; set; }
  public long TotalSeconds { get; set; }
  public string Text { get; set; }
  public double Distance { get; set; }
}

所附下載中的範例程式碼也包含擴充方法具有下列簽章:

public static ObservableCollection
  <ItineraryItemDisplay> ToDisplay(this 
  ObservableCollection<ItineraryItem> items)

這個方法中的程式碼迴圈的所有項目、 將重要的值寫入至新的 ItineraryItemDisplay 物件,也會持續追蹤的 [索引] 屬性中的目前步驟計數。 最後,ItineraryItemDisplayConverter 會負責在資料繫結期間轉換。 您可能已經注意到在 7,好好格式化每個 itinerary 的步驟 (城市和街道標示以粗體顯示) 使用自訂的控制項稱為 ItineraryItemBlock。 其唯一的目標是要將 ItineraryItem 的文字格式化以初始狀態的方式。 在圖 7,您也可以查看一些額外的資訊,內含的藍色區塊,但這是一般資料繫結:

[TemplatePart(Name = "ItemTextBlock", Type = 
  typeof(TextBlock))] public class
  ItineraryItemBlock : Control

TemplatePart 屬性定義應該會出現在控制項範本中的項目,也應該是哪一類型的項目。 在這種情況下,它應該呼叫 ItemTextBlock TextBlock:

<Style TargetType="controls:ItineraryItemBlock" x:Key="ItineraryItemBlock">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType=
        "controls:ItineraryItemBlock">
        <TextBlock x:Name="ItemTextBlock" 
          TextWrapping="Wrap" 
          LineStackingStrategy=
          "BlockLineHeight" LineHeight="43" />
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

選擇 TextBlock 的原因很明顯。 使用 TextBlock Inlines 屬性,您可以將內容新增到 TextBlock 在程式碼中。 OnApplyMethod 可以將它覆寫在自訂控制項中,而且這是當您會想要取得的 ItemTextBlock ahold (請參閱圖 8)。

圖 8尋找 TextBlock

/// <summary> 
/// When the template is applied, find the textblock.
/// </summary> 
public override void OnApplyTemplate() 
{ 
  base.OnApplyTemplate(); 
  
  // Get textblock.
textBlock = GetTemplateChild("ItemTextBlock") as TextBlock; 
  if (textBlock == null) 
    throw new InvalidOperationException
      ("Unable to find 'ItemTextBlock' TextBlock in the template."); 
  
  // Set the text if it was assigned before loading the template.
if (!String.IsNullOrEmpty(Text)) 
    SetItinerary(Text); 
}

將 dissected 的 ItineraryItem Text 屬性,並將用來填滿此 TextBlock 與一些額外的格式設定。 事實上,這很容易進行,因為 [文字] 屬性包含稍微比一般文字。 某些部分放在 XML 標記:

<VirtualEarth:Action>Turn</VirtualEarth:Action> 
  <VirtualEarth:TurnDir>left</VirtualEarth:TurnDir>onto  
  <VirtualEarth:RoadName>Guido Gezellestraat
  </VirtualEarth:RoadName>

我只想要反白顯示城市和道路名稱,因為我寫了小型的方法可刪除標記,例如 VirtualEarth:Action 或 VirtualEarth:TurnDir。 後擷取 TextBlock,SetItinerary 會呼叫此方法,而且這是 Inlines TextBlock 来加入的位置 (請參閱圖 9)。

圖 9加入要使用 SetItinerary 方法 TextBlock Inlines

// Read the input 
string dummyXml = String.Format(
  "<Itinerary xmlns:VirtualEarth=\"http://dummy\">{0}</Itinerary>", 
  itinerary); 
using (var stringReader = new StringReader(dummyXml)) 
{ 
  // Trace the previous element.
string previousElement = ""; 

  // Parse the dummy xml.
using (var xmlReader = XmlReader.Create(stringReader)) 
  { 
    // Read each element.
while (xmlReader.Read()) 
    { 
      // Add to textblock.
if (!String.IsNullOrEmpty(xmlReader.Value)) 
      { 
        if (previousElement.StartsWith("VirtualEarth:")) 
        { 
          textBlock.Inlines.Add(new Run() 
            { Text = xmlReader.Value, FontWeight = FontWeights.Bold }); 
        } 
        else 
        { 
          textBlock.Inlines.Add(new Run() { Text = xmlReader.Value }); 
        } 
      } 

      // Store the previous element.
if (xmlReader.NodeType == XmlNodeType.Element) 
        previousElement = xmlReader.Name; 
      else 
      previousElement = ""; 
    } 
  } 
}

如您在先前 XML 文字範例中所見,文字的不是每個部分被包含在 XML 項目。 若要能夠在 XmlReader 中使用這段文字,首先要是包含在空的 XML 項目。 這可讓我們建立新 XmlReader 以這段文字。 使用 XmlReader Read 方法,您可以循環的 XML 字串的每個部分。

您可以根據節點類型,圖中的 XML 字串的目前位置。 比方說,請考慮下列項目:<VirtualEarth:RoadName> Guido Gezellestraat </VirtualEarth:RoadName>。 使用 XmlReader Read 方法,您將有三個反覆項目。 第一個是 <VirtualEarth:RoadName> 而且這是 XmlNodeType.Element。 因為目標是要格式化的道路中的城市粗體,這是執行物件加入至 TextBlock Inlines 字體粗細] 設定為粗體的位置。 只將執行物件加入至 Inlines 將某些文字附加至 TextBlock。

在任何其他情況下,您不需要設定格式化的而且這是您要新增標準的執行物件,其中包含卻不需要任何格式設定的屬性集合的唯一文字]。

這是為自訂控制項。 使用自訂的 DataTemplate ListBox 的各個顯示整個 ItineraryItemDisplay 筆記錄。 這個 DataTemplate 也包含自訂控制項的參考 (請參閱圖 10)。

圖 10Listbox 的各個自訂 DataTemplate 中的整個 ItineraryItemDisplay 筆記錄

<!-- Template for a full item (includes duration and time) --> 
<DataTemplate x:Key="ItineraryItemComplete"> 
  <Grid Height="173" Margin="12,0,12,12"> 
    <!-- Left part: Index, Distance, Duration.
--> 
    <Grid HorizontalAlignment="Left" Width="75"> 
      <Grid.ColumnDefinitions> 
        <ColumnDefinition Width="25*" /> 
        <ColumnDefinition Width="25*" /> 
        <ColumnDefinition Width="25*" /> 
        <ColumnDefinition Width="25*" /> 
      </Grid.ColumnDefinitions> 
      <Grid.RowDefinitions> 
        <RowDefinition Height="50*"></RowDefinition> 
        <RowDefinition Height="20*"></RowDefinition> 
        <RowDefinition Height="20*"></RowDefinition> 
      </Grid.RowDefinitions> 

      <!-- Gray rectangle.
--> 
      <Rectangle Grid.ColumnSpan="4" Grid.RowSpan="3" Fill="#FF0189B4" /> 

      <!-- Metadata fields.
--> 
      <TextBlock Text="{Binding Index}" 
        Style="{StaticResource ItineraryItemMetadata}"    
        Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" /> 
      <TextBlock Text="{Binding Distance, 
        Converter={StaticResource kilometers}}" 
        Style="{StaticResource ItineraryItemMetadata}" 
        FontSize="{StaticResource PhoneFontSizeSmall}"                 
        Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" /> 
      <TextBlock Text="{Binding TotalSeconds, 
        Converter={StaticResource seconds}}" 
        Style="{StaticResource ItineraryItemMetadata}" 
        FontSize="{StaticResource PhoneFontSizeSmall}" 
        Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" /> 
    </Grid> 

    <!-- Right part to show directions.
--> 
    <StackPanel Margin="84,-4,0,0" VerticalAlignment="Top" > 
      <controls:ItineraryItemBlock Text="{Binding Text}" 
        Style="{StaticResource ItineraryItemBlock}" 
        FontSize="{StaticResource PhoneFontSizeLarge}" 
        Foreground="{StaticResource PhoneForegroundBrush}"   
        Padding="0,3,0,0" Margin="0,0,0,5" /> 
    </StackPanel> 
  </Grid> 
</DataTemplate>

現在的自訂控制項和樣式設定準備好,剩下來要做的唯一工作就是在樞紐分析表控制項和程式碼中實作。 如同我先前所述,在清單方塊中會使用自訂控制項和 DataTemplate:

<controls:PivotItem Header="Directions">
  <ListBox ItemsSource=
    "{Binding Itinerary, Converter={StaticResource itineraryConverter}}" 
    Grid.RowSpan="2" ItemTemplate="{StaticResource ItineraryItemComplete}" />
</controls:PivotItem>

這個清單方塊 ItemSource 繫結至 [行程] 屬性中,而這是如何填入屬性; 之後,ItineraryItemDisplayConverter 就會執行其餘。 您可以看到,使用自訂控制項和一些樣式,您可以採取路由服務的 itinerary 資料,並讓使用者吸引人:

private void OnRouteComplete(object sender, CalculateRouteCompletedEventArgs e)
{
  if (e.Result != null && e.Result.Result != null && 
    e.Result.Result.Legs != null & e.Result.Result.Legs.Any())
  {
    ...
Itinerary = e.Result.Result.Legs.FirstOrDefault().Itinerary;
  }
}

找出您目前的位置

在前一個範例中,您學到如何使用 Geocode 及路由服務,以取得指示從 a 點到 b 點,以及如何以視覺化方式檢視這些指示。 現在是時候來看看 geolocation API。

GeoCoordinateWatcher 是用來找出目前 GPS 座標的類別:

coordinateWatcher = new GeoCoordinateWatcher
  (GeoPositionAccuracy.High); coordinateWatcher.StatusChanged += 
  new EventHandler<GeoPositionStatusChangedEventArgs>(OnCoordinateUpdate); 
coordinateWatcher.Start();

GeoCoordinateWatcher 會透過不同階段後執行 Start 方法,但當狀態設定為已完成準備工作,您就可以存取到目前的位置。 最佳作法是在使用完後呼叫 Stop 方法使用 GeoCoordinateWatcher:

private void OnCoordinateStatusChanged(object sender,  
  GeoPositionStatusChangedEventArgs e) 
{ 
  if (e.Status == GeoPositionStatus.Ready) 
  {
    coordinateWatcher.Stop();
 
    // Get position.
fromLocation = coordinateWatcher.Position.Location;
    LocationLoaded(); 
  } 
}

現在範例應用程式也會提供能夠識別位置的功能。 GeoCoordinateWatcher 也會公開可讓您監視位置變更時的 PositionChanged 事件。 如果您要建立顯示方向的應用程式,您可以使用的位置變更自動捲動以檢視每個步驟,甚至播放音效,根據在 [文字] ItineraryItem VirtualEarth:Action。 您將最後會得到實際 GPS 瀏覽應用程式。

您正在偵錯應用程式使用 Windows 電話 7 模擬器嗎? 如果您正在測試您的應用程式 geolocalization 功能,您可能會擠開到 GeoCoordinateWatcher 狀態的小問題:它將永遠停留在 NoData 上,並不會再變更為 [可。 這就是為什麼請務必您針對撰寫程式碼 (IGeoPositionWatcher <GeoCoordinate>) 的介面,而且不會持續實作 (GeoCoordinateWatcher)。 在 Tim Heuer 的部落格文章 (bit.ly/cW4fM1),您可下載它們便會模擬實際的 GPS 裝置的 EventListGeoLocationMock 類別。

EventListGeoLocationMock 類別可接受必須及時模擬使用者的座標的 GeoCoordinateEventMocks 的集合。 這可讓您測試使用者的位置和移動:

GeoCoordinateEventMock[] events = new GeoCoordinateEventMock[]  
{  
  new  GeoCoordinateEventMock { Latitude = 50, Longitude = 6, 
    Time = new TimeSpan(0,0,5) }, 
  new  GeoCoordinateEventMock { Latitude = 50, Longitude = 7, 
    Time = new TimeSpan(0,15,0) } 
};
  
IGeoPositionWatcher<GeoCoordinate>  coordinateWatcher = 
  new EventListGeoLocationMock(events); coordinateWatcher.StatusChanged += 
  new EventHandler<GeoPositionStatusChangedEventArgs>(...); 
coordinateWatcher.Start();

根據裝置名稱,您無法判斷應用程式是否正在執行實際的裝置或模擬器決定要使用哪一個 IGeoPositionWatcher 上。 尋找擴充屬性 」 裝置名稱 」,而當在模擬器中執行應用程式會永遠設為 XDeviceEmulator:

private static bool IsEmulator() 
{ 
  return (Microsoft.Phone.Info.DeviceExtendedProperties.GetValue("DeviceName") 
    as string) == "XDeviceEmulator"; 
}

或者,在 Dragos Manolescu 的部落格 (bit.ly/h72vXj),您可以找到 mock Windows 電話 7 事件資料流使用因應之道的擴充功能、 或 Rx 的另一種方法。

實際的應用程式和效能

當您正在建立的應用程式,您想要賣出時,顯然很吸引人給使用者。 使用者會想具有最棒的功能的快速應用程式。 前述範例顯示您需要呼叫幾個 Web 服務方法,並處理幾個非同步事件之前,您可以向使用者顯示某些結果。 別忘了行動裝置上執行應用程式,而且一般 Wi-fi 連線永遠無法使用。

減少資料將透過網路與 Web 服務呼叫,可以加速您的應用程式。 在 「 簡介 」 小節,我討論了提供功能表和定位感知功能的餐廳應用程式。 如果您要建立這類應用程式,可能會提供功能表和提升至電話定域機組中執行的服務。 為何不使用這項服務來執行複雜計算,而不是在電話上執行它們? 以下是範例:

[ServiceContract] 
public interface IRestaurantLocator 
{ 
  [OperationContract] 
  NearResult GetNear(Location location); 
}

您可以建立使用使用者的目前位置的服務。 服務將會啟動 (此範例會使用 Parallel.ForEach) 有些執行緒並同時計算這個位置和其他餐廳之間的距離 (請參閱圖 11)。

圖 11計算使用者的位置與三個附近餐廳之間的距離

public NearResult GetNear(BingRoute.Location location) 
{ 
  var near = new NearResult(); 
  near.Restaurants = new List<RestaurantResult>(); 
  
  ...
Parallel.ForEach(restaurants, (resto) => 
  { 
    try 
    { 
      // Build geo request.
var geoRequest = new BingGeo.GeocodeRequest(); 
      ...
// Get the restaurant's location.
var geoResponse = geoClient.Geocode(geoRequest); 
  
      // Restaurant position.
if (geoResponse.Results.Any()) 
      { 
        var restoLocation = 
          geoResponse.Results.FirstOrDefault().Locations.FirstOrDefault(); 
        if (restoLocation != null) 
        { 
          // Build route request.
var fromWaypoint = new Waypoint(); 
          fromWaypoint.Description = "Current Position"; 
          ...; 
  
          var toWaypoint = new Waypoint(); 
          ...
// Create the request.
var routeRequest = new RouteRequest(); 
          routeRequest.Waypoints = new Waypoint[2]; 
          routeRequest.Waypoints[0] = fromWaypoint; 
          routeRequest.Waypoints[1] = toWaypoint; 
          ...
// Execute the request.
var routeClient = new RouteServiceClient(); 
          var routeResponse = routeClient.CalculateRoute(routeRequest); 
  
          // Add the result to the result list.
if (routeResponse.Result != null) 
          { 
            var result = new RestaurantResult(); 
            result.Name = resto.Name; 
            result.Distance = routeResponse.Result.Summary.Distance; 
            result.TotalSeconds = routeResponse.Result.Summary.TimeInSeconds;
            results.Add(result); 
          } 
        } 
      } 
    } 
    catch (Exception ex) 
    { 
      // Take appropriate measures to log the error and/or show it to the end user.
} 
  }); 
  

  // Get the top 3 restaurants.
int i = 1;
  var topRestaurants = results.OrderBy(o => o.TotalSeconds)
                       .Take(3)
                       .Select(o => { o.Index = i++; return o; });
 
  // Done.
near.Restaurants.AddRange(topRestaurants);
  return near;
 
}

以平行方式餐廳裡每個餐廳中執行迴圈,每個餐廳的位置將轉換為使用 GeocodeServiceClient geolocation。 使用此位置和使用者的位置,路由會計算使用 RouteServiceClient 這些點之間。 最後,摘要的路由的 TotalSeconds 屬性用來尋找三個最接近餐廳,而那些會傳送到裝置。

此處的優點是計算會執行一次 (使用 Parallel.ForEach 和電腦的資源而定),而且一旦它們完成時,只將相關的資料移至 Windows 電話。 取向,您就能夠上手差異; 行動應用程式只會呼叫單一的 Web 服務方法,並且只有一些資料經過電線。

除了,程式碼,並在 Windows 電話 7 的非同步呼叫會降低大幅,如下所示:

var client = new RestaurantLocatorClient(); 
client.GetNearCompleted += new EventHandler<
  GetNearCompletedEventArgs>(OnGetNearComplete); 
client.GetNearAsync(location);

圖 12在電話上會顯示附近餐廳的顯示方式。

圖 12的三個附近餐廳電話顯示

服務商場送出

我想要說的最後項目是 Windows 電話 7 服務商場送出程序。您的應用程式需要傳遞一組被允許在服務商場的需求。這些需求的其中一個在應用程式資訊清單檔中定義應用程式的功能。如果您決定要使用 GeoCoordinateWatcher,您也必須在應用程式資訊清單檔中定義的 ID_CAP_LOCATION 能力。

MSDN 程式庫] 頁面上,「 如何:使用功能偵測工具為 Windows 電話 」 (bit.ly/hp7fjG),說明如何使用功能偵測工具來偵測應用程式所使用的所有功能。請務必閱讀本文之前您送出您的應用程式!

範例應用程式

這個應用程式的程式碼下載包含內含兩個專案的方案。其中一個是包含控制項、 擴充方法的類別程式庫和樣式,您可以輕易地整合您的專案。第二個是結合了一個小型應用程式中的所有範例範例 Windows 電話 7 樞紐分析應用程式。

Sandrino Di Mattia 是 Microsoft 企業家。在其工作身在 RealDolmen 的技術顧問,他將整合 Microsoft 技術,以及產品建立適用於客戶和其企業的解決方案。在他閒餘的時間,他參與比利時使用者群組和他的部落格,在撰寫文件blog.sandrinodimattia.net

感謝至下列技術專家檢閱這份文件:Dragos Manolescu