Share via


本文章是由機器翻譯。

Bing Map Apps

使用 Bing Map App SDK 建置即時交通系統應用程式

Luan Nguyen

下載代碼示例

六月份的 Tech•Ed 2010 大會上,Microsoft 發佈了免費的 Bing Map 應用程式 SDK,開發人員可以用它在位於 bing.com/maps/explore/ 的 Bing Maps 探討網站上編寫以地圖為中心的應用程式。

這為各種組織、公司或愛好者提供了大量的機會,可以在 Bing Maps 中創建自己的地圖繪製體驗。對於公司而言,可以編寫應用程式用於宣傳產品或完善其線上服務。例如,bestparking.com 開發了一個“停車查找器”應用程式,可以説明您使用其資料庫查找所在城市的所有停車場。

如需查看地圖應用程式的外觀,請轉到 Bing Maps 探討網站,然後按一下頁面左下角附近的“MAP APPS”按鈕。此時將打開地圖應用程式庫,其中顯示可用的應用程式。這個庫中已經擁有了眾多應用程式,並且數量仍在增長。

在本文中,我將介紹使用 Bing Map 應用程式 SDK 編寫地圖應用程式所需執行的操作。我將引導您完成示例應用程式的開發,該應用程式顯示華盛頓州金恩郡的公車到站即時資訊。此應用程式的靈感源自熱門的 One Bus Away 應用程式。

最終產品

在開始研究代碼之前,我們先瞭解一下最終地圖應用程式的外觀。在啟動後,如果將地圖縮放到金恩郡地區,則該地圖將在左側面板中顯示地圖視距內所有公車站清單(請參見圖 1)。

圖 1 OBA 應用程式在左側面板中顯示公車資訊,在地圖上顯示圖釘

每個公車站上提供了該站停靠的所有公車的相關資訊。按一下各個車站的“到站時間”超連結可以打開第二級面板,其中顯示即將到站的到站時間(請參見圖 2)。

圖 2 特定公車站的公車到站時間

該應用程式還為地圖上的每個公車站添加了醒目的圖釘(以字母 B 標記)。您可以與圖釘交互以啟動快顯視窗,在該視窗中可以針對特定公車站調用常用命令,如獲取該車站往來方向的駕駛或步行路線(請參見圖 3)。

圖 3 按一下圖釘可顯示提供附加資訊的彈出 UI

下載 Bing Map 應用程式 SDK

在首次開始編寫地圖應用程式之前,您需要從 connect.microsoft.com/bingmapapps 安裝 Bing Map 應用程式 SDK。該 SDK 提供了引用程式集、一個示例專案和離線文檔。此外,由於 SDK 基於 Silverlight 4,您還需要安裝 Silverlight 4 Tools for Visual Studio 2010,可從 silverlight.net/getstarted/ 下載。

地圖應用程式基礎知識

Bing Maps 探討網站在構建之時便將可擴展性作為最高優先順序。Bing Maps 小組希望能使全球開發人員方便地增加對其有用以及可能會為其他人提供説明的功能。在這個目標下,地圖應用程式 是一種利用 Bing Maps 核心部分提供的構建塊服務的具體功能。在預設的 Bing Maps 網站中,行車路線、企業搜索和位置搜索功能均構建自相同的構建塊。

考慮到這一目標,Bing Maps 小組決定構建可擴展框架來支援地圖應用程式的概念。在這個框架上編寫時,地圖應用程式利用了具有依賴關係注入樣式的方法提供對功能的訪問,這與 Managed Extensibility Framework (MEF) 類似。

該框架的核心部分是外掛程式的概念。外掛程式是一種擴展單元,允許開發人員以可維護的分離方式增加功能。在 MEF 術語中,可以將其看作等同于一個可組合部分。外掛程式使用屬性來聲明所需的導入以及要共用的匯出。在運行時,框架將解析外掛程式之間的依賴關係,並將具有相同約定的導入和匯出匹配。

要編寫地圖應用程式,您可以創建 Silverlight 4 類庫,其中包含且只包含一個基本外掛程式類的子類。為了執行有用的操作,外掛程式有可能導入多種內置約定,以便您訪問不同核心服務。SDK 文檔中有一部分專門列出了由框架匯出的所有內置約定。

要引用外掛程式類和所有內置約定類型,您的專案需要從 SDK 引用提供的程式集。图 4 簡要介紹了這四種程式集。

圖 4 地圖應用程式專案的引用程式集

程式集名稱 說明
Microsoft.Maps.Plugins.dll 包含基本外掛程式類和相關的導入/匯出屬性類。
Microsoft.Maps.Core.dll 包含 Bing Maps 提供的所有約定類型。
Microsoft.Maps.MapControl.Types.dll 包含處理地圖控制項所需的類型。
Microsoft.Maps.Extended.dll 包含與 StreetSide 地圖模式交互的類型。

訪問 One Bus Away API

為了獲取即時的公車到站資訊,我利用了對公共開放的 One Bus Away REST API(下文中簡稱為 Oba API)。有關 Oba API 的詳細資訊,請參見 code.google.com/p/onebusaway/wiki/OneBusAwayRestApi。(請注意,該 API 當前可免費用於非商業用途,但必須先註冊應用程式金鑰才可訪問。在本文的可下載代碼中,我刪除了分配給我的金鑰,因此,如果您想自行編譯和試用應用程式,則需要用自己的金鑰替換。)

在 Silverlight 應用程式嘗試訪問其他域上的 Web 服務時,Silverlight 運行時要求必須顯式選擇採用服務,以允許跨域訪問。服務在承載服務的域的根處放置 clientaccesspolicy.xml 或 crossdomain.xml 檔,用於指示其許可。有關這兩個檔方案的更多詳細資訊,請參見 msdn.microsoft.com/library/cc197955%28VS.95%29。幸運的是,Oba API 提供了 crossdomain.xml 檔,這使得我的地圖應用程式可以在 Silverlight 代碼中調用它。

在可下載的解決方案中,您可以看到兩個庫物件:ObaApp 和 ObaLib。ObaApp 是主專案,其中包含地圖應用程式外掛程式並引用 ObaLib。ObaLib 是另一個類庫專案,其中封裝了所有説明程式類,用於與 Oba API 通信。我將其做成了一個單獨的庫,需要時可以在不同專案中方便地重用。本文並不詳細介紹此庫中的每個類,但建議您查看原始程式碼以瞭解此處各個類的詳細資訊。

要瞭解的最重要的類是 ObaSearchHelper 類,該類提供了用於對 Oba API 進行查詢的便利方法和事件:

public sealed class ObaSearchHelper
{
  public void SearchArrivalTimesForStop(string stopId);
  public event EventHandler<BusTripsEventArgs>
    SearchArrivalTimesForStopCompleted;


  public void SearchStopsByLocation(double latitude, double longitude, 
    double radius, int maxResultCount);
  public event EventHandler<BusStopsEventArgs> 
    SearchStopsByLocationCompleted;

  // more method/event pairs
  // ...
}

在此類中可以方便地找出所用模式。 對於 Oba API 的每個 REST 端點,存在一個方法可用於觸發搜索,而另一個對應事件用於通知該搜索完成。 事件的訂閱者可以從事件參數物件獲取結果。

在準備好這個便利的類之後,我將逐一說明 ObaApp 專案中的主類。

定義地圖實體

編寫地圖應用程式時,要做的第一件事情是定義地圖實體。 下麵是來自 SDK 文檔的實體定義:“實體是地圖上存在地理關聯的專案。 地圖實體可以是點、折線、多邊形或圖像覆蓋。”一條簡單的經驗法則是,如果要在地圖上放置 某物 ,則需要一個實體的實例來表示該物。 通常,您需要創建實體的子類來為實體添加附加屬性和自訂邏輯。 在我的應用程式中,由於希望顯示公車站的位置,我編寫了 BusStopEntity 類來表示公車站。 图 5 顯示了 BusStopEntity 類。

圖 5 BusStopEntity 類

public class BusStopEntity : Entity
{
  private readonly DependencyProperty NamePropertyKey =
    Entity.RetrieveProperty(“Name”, typeof(string));

  private BusStop _busStop;

  // Short name of this bus stop
  public string Name
  {
    get { return _busStop.Name; }
  }

  // Unique id of this bus stop
  public string StopId
  {
    get { return _busStop.Id; }
  }

  public BusStopEntity(BusStop busStop, 
    PushpinFactoryContract pushpinFactory)
  {
    _busStop = busStop;

    // Set the location of this Entity
    Location location = 
      new Location(_busStop.Latitude, _busStop.Longitude);
    this.Primitive = pushpinFactory.CreateStandardPushpin(location, “B”);

    // Set the name of this Entity
    this.SetValue(NamePropertyKey, _busStop.Name);
  }
}

我的 BusStopEntity 類公開了兩個屬性:Name 和 StopId,稍後這兩個屬性將用於對 UI 控制項的資料綁定。 這些值來自基礎 BusStop 實例。 BusStop 類在 ObaLib 專案中定義,封裝了公車站的資料,這些資料獲取自 Oba API。

在構造函數中,我還設置了 Primitive 和 Name 屬性。 Primitive 屬性工作表示實體的類型(例如,點、多邊形或折線)和位置。 我將位置設置為 BusStop 實例的經度和緯度值。 此外,將 Primitive 屬性設置為 PushpinFactoryContract.CreateStandardPushpin 的返回值可以讓我的實體具有標準 Bing Maps 圖釘的外觀和使用感受。 PushpinFactoryContract 類型提供了創建通用圖釘 UI 元素的便利方法。 如果您希望創建自訂的圖釘形狀,則無需使用它。 此處我只是在圖釘內部放置了字母 B(代表公車),而您可以使用圖像或任意 UIElement。

雖然我在子類中定義了 Name 屬性,但 SDK 中的其他類型不會識別該屬性,這些類型對我的屬性一無所知。 因此,我還將相同的值設置給通過調用 Entity.RetrieveProperty(“Name”, typeof(string)) 檢索到的 Name 依賴關係屬性。 採用這種方法,使得其他功能可以檢索公車站實體的名稱。

編寫主外掛程式

如前所述,您需要一個外掛程式派生的類以表示地圖應用程式。 图 6 顯示了我的名為 ObaPlugin 的外掛程式。 我的外掛程式總共導入六個約定。 請注意,由 Bing Maps 提供的所有約定都遵守 Microsoft 的命名約定/*。 SDK 文檔提供了可以導入的所有約定的詳細說明,如圖 7 中所示。

圖 6 在 ObaPlugin 類中聲明的導入

public class ObaPlugin : Plugin
{

  #region Imports

  [ImportSingle(“Microsoft/LayerManagerContract”, ImportLoadPolicy.Synchronous)]
  public LayerManagerContract LayerManager { get; set; }

  [ImportSingle(“Microsoft/PopupContract”, ImportLoadPolicy.Synchronous)]
  public PopupContract PopupContract { get; set; }

  [ImportSingle(“Microsoft/ConfigurationContract”, 
    ImportLoadPolicy.Synchronous)]
  public ConfigurationContract ConfigurationContract { get; set; }

  [ImportSingle(“Microsoft/MapContract”, ImportLoadPolicy.Synchronous)]
  public MapContract Map { get; set; }

  [ImportSingle(“Microsoft/PushpinFactoryContract”, 
    ImportLoadPolicy.Synchronous)]
  public PushpinFactoryContract PushpinFactory { get; set; }

  [ImportSingle(“Microsoft/ContributorHelperContract”, 
    ImportLoadPolicy.Synchronous)]
  public ContributorHelperContract ContributorHelper { get; set; }

  #endregion

圖 7 ObaPlugin 導入的約定

約定類型 說明
LayerManagerContract 允許外掛程式添加或刪除地圖層。
PopupContract 允許外掛程式在使用者游標
懸停或者按一下實體/圖釘
時顯示彈出 UI。
ConfigurationContract 允許外掛程式在運行時從設定檔動態載入配置值。
MapContract 允許外掛程式控制地圖。
PushpinFactoryContract 允許外掛程式為其實體呈現標準圖釘。
ContributorHelperContract 允許外掛程式創建非同步提供程式或根據需求載入的提供程式。 (稍後我將使用此説明程式將“Driving directions”和“StreetSide”提供程式連結添加到快顯視窗中。)

聲明瞭導入之後,我將覆蓋虛擬 Initialize 方法:

public override void Initialize()
{
  // Obtain the application key from configuration file
  IDictionary<string, object> configs = 
    ConfigurationContract.GetDictionary(this.Token);
  string appKey = (string)configs[“ApplicationKey”];
  _searchHelper = new ObaSearchHelper(appKey);
  _searchHelper.SearchStopsByLocationCompleted += 
    OnSearchBusStopsCompleted;
  _searchHelper.SearchArrivalTimesForStopCompleted += 
    OnSearchArrivalTimesComplete;

  // Update the bus stop every time map view changes
  Map.TargetViewChangedThrottled += (s, e) => RefreshBusStops();
}

在外掛程式實例構建並且滿足所有聲明的導入後,將調用且只調用一次此方法。 這是執行依賴于導入約定的所有初始化工作的絕佳位置。 如果外掛程式具有任何匯出,則您還必須確保所有匯出的屬性在 Initialize 返回時已完全產生實體。

我首先通過導入的 ConfigurationContract 實例從設定檔獲取應用程式金鑰,然後將其傳遞到 ObaSearchHelper 構造函數。 通過將應用程式金鑰放在設定檔中,我可以方便地隨時進行更改而無需重新編譯專案。

接下來要做的事情是從 MapContract 實例掛接事件 TargetViewChangedThrottled。 此事件在地圖視圖每次將要更改時引發,這種更改可以是程式設計方式,也可以通過使用者交互。 正如名稱所表明,該事件在內部限制,因此在短期運行中不會觸發過多次數。 所以,如果要保持實體與地圖視圖同步,這是一個絕佳的事件。 在本例中,我調用了 RefreshBusStops 方法以刷新公車站清單。 以下為 RefreshBusStops 的定義:

internal void RefreshBusStops()
{
  if (Map.ZoomLevel >= 14)
  {
    // Search within the radius of 1km, maximum 150 results
    _searchHelper.SearchStopsByLocation(
      Map.Center.Latitude, Map.Center.Longitude, radius: 1000, 150);
  }
  else
  {
    // Clear all bus stops
    ClearBusStops();
  }
}

此處,我檢查了當前地圖縮放級別至少為 14,然後發佈了對 SearchStopsByLocation 方法的調用,這將返回在地圖中心附近指定半徑內的所有公車站清單。 否則,如果縮放級別小於 14,則意味著地圖縮放級別還不夠接近城市級別,則將清除所有公車站。

SearchStopsByLocation 方法完成時(非同步),將引發 SearchStopsByLocationCompleted 事件,如圖 8 中所示,我之前已在 Initialize 方法中訂閱該事件。

圖 8 SearchBusStopsCompleted 事件的處理常式

private int _searchCount;
private Dictionary<BusStop, int> _busStopCache = 
  new Dictionary<BusStop, int>();
private ObaLayer _layer;

private void OnSearchBusStopsCompleted(object sender, BusStopsEventArgs e)
{
  _searchCount++;

  // Contains new bus stops not present in the current view 
  Collection<BusStopEntity> addedEntities = 
    new Collection<BusStopEntity>();
  foreach (BusStop stop in e.BusStops)
  {
    // If this bus stop is not in the cache, it is a new one
    if (!_busStopCache.ContainsKey(stop))
    {
      addedEntities.Add(new BusStopEntity(stop, PushpinFactory));
    }

    // Marks this bus stop as being in the current search
    _busStopCache[stop] = _searchCount;
  }

  // Contains the old bus stops 
  // that should be removed from the current view
  Collection<BusStopEntity> removedEntities = 
    new Collection<BusStopEntity>();
  foreach (BusStopEntity bse in _layer.Entities)
  {
    // This bus stop belongs to the previous search, 
    // add it to removed list
    if (_busStopCache[bse.BusStop] < _searchCount)
    {
      removedEntities.Add(bse);
      _busStopCache.Remove(bse.BusStop);
    }
  }

// Tells the layer to add new in-view entities 
// and remove out-of-view ones 
      _layer.RefreshEntities(addedEntities, removedEntities);
}

請注意類型為 ObaLayer 的物件,我將在下麵的部分中予以說明。 目前可以肯定地說,該物件負責管理左邊面板的內容以及地圖上的實體。

請注意,我使用了 Dictionary<BusStop, int>物件來説明我跟蹤當前顯示的公車站清單。 通過此字典的説明,每次從服務調用中獲取一組全新的公車站時,我可以快速確定尚未顯示的新車站,以及由於地圖視圖更改現在位於視圖之外的舊車站。

您可能會有疑問,為什麼不清除所有當前公車站並將新的一組公車站一起顯示。 這是出於性能考慮。 如果採用了上述方法,則必須強制重新創建所有圖釘控制項,即使新舊地圖視圖上有許多圖釘處於相同位置。 更糟糕的是,在刪除圖釘並快速添加回來的過程中,使用者會看到快速刷新。 而我採用的方法可以消除這些缺點。

在圖層上顯示內容和圖釘

外掛程式類表示地圖應用程式,但是如果您希望顯示其 UI 表示形式,則需要創建圖層。 圖層是一個抽象的概念,允許您將實體作為圖釘(或者折線、多邊形)放置在地圖上,並在左側面板中顯示自訂 UI 內容。 還可以選擇將任意 UI 覆蓋放置在地圖之上,但在我的應用程式中不需要。 大部分應用程式只有一個圖層。

如果您曾經用過 Photoshop,則會發現其概念與 Photoshop 圖層非常相似。 “歷史記錄”按鈕(位於頁面的左下角)顯示所有當前載入圖層的清單。 您可以選擇顯示或隱藏任何圖層,或者可以將某個圖層設置為活動。 圖層成為活動時,其面板 UI 元素顯示在左側面板中,並且其所有實體都將置於 z 索引堆疊的前面。

在代碼中,圖層是抽象 Layer 類的子類。 如圖 9 中所示,ObaPlugin 創建和管理 ObaLayer 類的實例,該實例派生自 Layer。

圖 9 ObaLayer 類

internal class ObaLayer : Layer
{
  private ObaPlugin _parent;
  private BusStopsPanel _resultPanel;
  private ObservableCollection<BusStopEntity> _busStopEntities;

  public ObaLayer(ObaPlug-in parent) : base(parent.Token)
  {
    _parent = parent;

    // Contains the set of active bus stop entities 
    // for data binding purpose
    _busStopEntities = new ObservableCollection<BusStopEntity>();
    _resultPanel = new BusStopsPanel(parent, this);
    _resultPanel.DataContext = _busStopEntities;

    this.Title = “Bus stops”;
    this.Panel = _resultPanel;
  }
  ...
}

在 ObaLayer 的構造函數中,我為圖層設置了標題,該標題顯示在左側面板的頂部。 我還將 Panel 屬性設置為 BusStopsPanel 使用者控制項的實例,如果我的圖層活動,該控制項將佔據整個左側面板區域。 使用者控制項的 DataContext 屬性設置為 ObservableCollection<Entity>的實例。

那麼,如何使圖層顯示? 使用圖 10 中所示的 ObaPlugin 完成。

圖 10 ShowResultLayer 方法向使用者顯示 ObaLayer 實例

public override void Activate(IDictionary<string, string> activationParameters)
{
  ShowResultLayer();
  RefreshBusStops();
}

private void ShowResultLayer()
{
  if (_layer == null)
  {
    _layer = new ObaLayer(this);
  }

  if (LayerManager.ContainsLayer(_layer))
  {
    LayerManager.BringToFront(_layer);
  }
  else
  {
    LayerManager.AddLayer(_layer);
  }
}

每次通過地圖應用程式庫啟動地圖應用程式時,將調用 override Activate 方法。 為顯示圖層,我引用了之前導入的 LayerManagerContract 類型。 LayerManagerContract 類定義方法以與圖層一起使用。 如果圖層已添加,則通過調用 BringToFront 方法將其設置為活動。 嘗試兩次添加相同圖層會導致異常。

圖 8 中,我調用了 ObaLayer.RefreshEntities 方法以更新公車站。 圖 11 顯示了其定義。

圖 11 ObaLayer.RefreshEntities 方法定義

public void RefreshEntities(
  ICollection<BusStopEntity> addedEntities,
  ICollection<BusStopEntity> removedEntities)
{
  foreach (BusStopEntity entity in removedEntities)
  {
    // Remove this pushpin from the map
    this.Entities.Remove(entity);
    // Remove this bus stop entry from the panel
    _busStopEntities.Remove(entity);
  }

  foreach (BusStopEntity entity in addedEntities)
  {
    // Add this pushpin to the map
    this.Entities.Add(entity);
    // Add this bus stop entry to the panel
    _busStopEntities.Add(entity);

    // Register this entity to have popup behavior
    _parent.PopupContract.Register(entity, OnPopupStateChangeHandler);
  }
}

為在地圖上添加或刪除實體,我使用了 Layer.Entities 集合屬性。 並且,對於每個新 BusStopEntity,我調用 PopupContract.Register 方法以在公車站圖釘上註冊彈出 UI。 Register 方法接受實體和回檔方法,該回檔方法在快顯控制項更改實體狀態時調用(請參見圖 12)。

圖 12 更改快顯控制項的狀態

private void OnPopupStateChangeHandler(PopupStateChangeContext context)
{
  if (context.State == PopupState.Closed)
  {
    return;
  }

  BusStopEntity entity = (BusStopEntity)context.Entity;
  context.Title = entity.Name;
  context.Content = “Bus numbers: “ + entity.BusRoutesAsString;

  // Only shows contributors link in the normal state of popup
  if (context.State == Pop-upState.Normal)
  {
    // Add Arrival times contributor
    context.Contributors.Add(new BusStopContributor(_parent, entity));

    Dictionary<string, object> parameter = new Dictionary<string, object>
    {
      {“Entity”, entity}
    };
    var contributorHelper = _parent.ContributorHelper;

    // Add Directions contributor
    context.Contributors.Add(contributorHelper.
CreateDemandLoadContributor(
      “Microsoft/DirectionsContributorFactoryContract”, 
      parameter, “Directions”));

    // Add Streetside contributor
    context.Contributors.Add(contributorHelper.CreateAsyncContributor(
      “Microsoft/StreetsideContributorFactoryContract”, parameter));
  }
}

在快顯控制項回檔內部,PopupStateChangeContext 類型的方法參數提供了對快顯控制項的當前狀態以及快顯控制項下的當前實體的訪問。 根據這些內容,我使用公車站名稱設置快顯控制項標題,使用 BusStopEntity.BusRoutesAsString 屬性設置快顯控制項內容,該屬性將返回停靠此特定公車站的公交線路清單,以逗號分隔。

如果快顯控制項處於 Normal 狀態,則還通過 Contributors 集合屬性將三個提供程式連結添加到快顯控制項,其中“Arrival times”提供程式屬性顯示此公車站的到站時間,Directions 提供程式調用行車路線功能,Streetside 內容提供程式切換到街道級別的地圖模式。

提供程式使用快顯控制項底部的超連結表示。 它允許地圖應用程式調用 Bing Maps 的一個特定核心功能。 要生成提供程式,您可以調用 ContributorHelperContract 類型的兩種方法之一:CreateAsyncContributor 或 CreateDemandLoadContributor。 這兩種方法均非同步返回代理提供程式,並延遲載入真實的提供程式實例。 唯一的差別是 CreateAsyncContributor 在返回時立即載入真實提供程式,而 CreateDemandLoadContributor 僅在首次調用代理提供程式連結時進行載入。

BusStopsPanel UserControl

BusStopsPanel 是一個 UserControl,負責在左側面板內顯示視圖內的公車站清單(如圖 1 所示)。 其中包含將 ItemTemplate 屬性設置為自訂 DataTemplate 的 ItemsControl 實例(請參見圖 13)。 請注意,通過將 ItemsPanel 屬性設置為使用 VirtualizingStackPanel,我為 ItemsControl 啟用了 UI 虛擬化模式。 通常,如果應用程式可能將數百個專案載入到其中,則對 ItemsControl 應用 UI 虛擬化是一種好方法。

圖 13 BusStopsPanel UserControl

<UserControl.Resources>
  <DataTemplate x:Key=”BusStopTemplate”>
    <Border Margin=”2,0,2,12”
      <StackPanel>
        <TextBlock Text=”{Binding Name}” FontSize=”14” FontWeight=”Bold”  
          TextWrapping=”Wrap” />
        <TextBlock Text=”{Binding BusRoutesAsString, StringFormat=’Bus numbers:
          {0}’}” FontSize=”12” TextWrapping=”Wrap” />
          <HyperlinkButton Content=”Arrival times” Click=”OnBusStopClick” 
            Style=”{StaticResource App.P2.Hyperlink}”         
              HorizontalAlignment=”Left” />
      </StackPanel>
    </Border>
  </DataTemplate>

  <ControlTemplate x:Key=”ScrollableItemsControl”    
    TargetType=”ItemsControl”>
    <ScrollViewer Style=”{StaticResource App.ScrollViewer}”  
      VerticalScrollBarVisibility=”Auto”>
      <ItemsPresenter />
    </ScrollViewer>
  </ControlTemplate>
</UserControl.Resources>
    
<ItemsControl 
  ItemsSource=”{Binding}”
  VirtualizingStackPanel.VirtualizationMode=”Recycling”
  ItemTemplate=”{StaticResource BusStopTemplate}” 
  Template=”{StaticResource ScrollableItemsControl}”>
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <VirtualizingStackPanel />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
</ItemsControl>

在公車站 DataTemplate 中,我添加了 HyperlinkButton 控制項,按一下該控制項時將觸發搜索相應公車站的到站時間:

private void OnBusStopClick(object sender, RoutedEventArgs e)
{
  FrameworkElement element = (FrameworkElement)sender;
  BusStopEntity entity = (BusStopEntity)element.DataContext;
  _plugin.SearchArrivalTimes(entity);
}

請注意,其 Style 屬性設置為來自 StaticResource 集合的物件,並且鍵設置為“App.P2.Hyperlink”。Bing Maps 提供了一組預設 UI 資源,如常用控制項的樣式和 ControlTemplates,以及 Bing Maps 自身使用的標準顏色和畫筆。 建議地圖應用程式作者對其 UI 元素應用這些資源,使其具有與本機 UI 元素相同的外觀和使用感受。 有關 Bing Maps 提供的所有資源,請參閱文檔。

SearchArrivalTimes 是 ObaPlugin 類的內部方法。 它調用 ObaSearchHelper.SearchArrivalTimesForStop 方法以檢索指定公車站的到達時間:

internal void SearchArrivalTimes(BusStopEntity _entity)
{
  _searchHelper.SearchArrivalTimesForStop(_entity.StopId, _entity);
}

搜索完成後,外掛程式將指示 ObaLayer 在左側窗格中顯示到站時間。 ObaLayer 通過動態更改其 Title 和 Panel 屬性來完成此操作。

地圖應用程式的測試和調試

完成編碼之後,您將需要測試應用程式。 Bing Maps 網站提供了開發人員模式,可通過在 URL 中附加“developer=1”查詢參數來啟用,如下所 https://www.bing.com/maps/explore/?developer=1. 在開發人員模式中時,您可以使用“地圖應用程式測試工具”測試地圖應用程式,該工具可通過相同地圖應用程式庫啟動。 使用地圖應用程式測試工具可以從本地硬碟驅動器中選擇外掛程式程式集。 然後,它將外掛程式載入到網站中,如同對所有本機外掛程式一樣。 要調試代碼,請在載入應用程式時將 VS.NET 與流覽器關聯。 請確保將調試代碼類型設置為“Silverlight”。

提交您的地圖應用程式

最後,在您確認對代碼滿意時,可以提交應用程式以正式發佈到 Bing Maps 網站上。 在 Bing Maps 帳戶中心 (bingmapsportal.com) 中,您可以提交新應用程式和查看以前提交應用程式的狀態。 SDK 文檔中提供了關於提交應用程式的詳細說明,並且列出了應用程式獲得批准所需滿足的要求清單。

倡議

這樣,您便完成了工作:成熟、即時傳輸應用程式,無需太多代碼。 使用 SDK 提供的構建塊編寫以地圖為中心的應用程式的體驗輕鬆且獲益頗多。 建議您立即下載 SDK、學習並開始編寫自己的地圖應用程式。

Luan Nguyen   在 Bing Maps 小組(以前稱為 Microsoft Virtual Earth)作為開發人員工作已接近四年。 他是 Shell 功能小組的成員,該小組負責 Bing Maps 網站和 Bing Map 應用程式 SDK 的框架 Shell。 最近他調到了 ASP.NET 小組。

衷心感謝以下技術專家對本文的審閱:Alan PaulinChris PendletonDan PolivyGreg Schechter