Windows 應用程式 SDK 與 WinUI 3 提供顯示地圖、偵測使用者位置及設置地理圍欄的 API 與控制。 利用這些功能來建立能顯示帶有圖釘的互動地圖、追蹤使用者位置,並在使用者進出地理區域時觸發動作的應用程式。
本文介紹每項能力,並包含一個完整的MapControl範例,將 、 GeolocatorGeofenceMonitor 、 整合於單一可用應用程式中。
使用 MapControl 顯示地圖
MapControl 顯示由 Azure 地圖服務 驅動的互動地圖。 你可以新增標記和圖層,並回應使用者的互動操作,例如平移、縮放和點擊。
MapControl 需要 Azure 地圖服務 帳號。 請參閱「管理您的 Azure 地圖服務 帳號」以建立帳號並取得服務代幣。
詳細使用說明請參閱 MapControl。
<MapControl x:Name="myMap"
MapServiceToken="YOUR_AZURE_MAPS_TOKEN"
Height="400" />
附註
UWP MapControl 與 Windows。Services.Maps API 已被棄用,未來版本的 Windows 可能無法使用。 WinUI 3 應用程式應該使用上述的新 MapControl。 欲了解更多資訊,請參閱 已棄用功能資源。
偵測使用者的位置
Windows。Devices.Geolocation API 讓您能取得裝置的地理位置。 這些 API 可在 UWP 和 Windows 應用程式 SDK(WinUI 3)應用程式中運作。 您可以:
- 使用 Geolocator.GetGeopositionAsync取得一次性位置。
- 使用 Geolocator.PositionChanged 事件來追蹤位置隨時間的變化。
- 使用 GeovisitMonitor監控到訪狀態變更,以低耗電方式掌握位置感知。
如需逐步操作指南,請參閱 取得使用者的位置。
設定地理柵欄
地楔定義了地理邊界。 當使用者進入或離開邊界時,應用程式會收到通知。 地理圍欄對於基於位置的提醒、警示或內容傳遞非常有用。
關於建立與監控地理圍欄的說明,請參見 「設置地理圍欄」。
定位能力與隱私
所有位置 API 都需要在您的應用程式套件資訊清單中宣告 Location 功能。 你也必須在執行時呼叫 Geolocator.RequestAccessAsync ,才能存取位置資料。
Windows 允許用戶透過設定>、隱私與安全>、位置資訊來控制哪些應用程式能存取他們的位置。 你的應用程式應該能處理使用者拒絕或撤銷位置存取的情況。
完整範例
以下範例將 MapControl、 Geolocator、 GeofenceMonitor 合併於單一 WinUI 3 視窗中。 如果沒有設定 Azure 地圖服務 鍵,地圖會優雅地退化,而地理定位和地理圍欄功能則持續運作。
先決條件
- Windows 應用程式 SDK 2.2 或更新版本
- 一個 Azure 地圖服務 金鑰 — 顯示地圖圖塊所需。 如果沒有有效的金鑰,MapControl 會渲染但地圖是空白的。
-
定位裝置能力宣告於
Package.appxmanifest:
<DeviceCapability Name="location" />
在執行應用程式前,先將你的 Azure 地圖服務 鍵數設為環境變數:
$env:AZURE_MAPS_KEY = "your-key-here"
MainWindow.xaml
左側為 300 像素寬的控制面板,包含按鈕和狀態文字,右側則為 MapControl。 當 Azure 地圖服務 鍵缺失時,會出現一個覆蓋層。
<?xml version="1.0" encoding="utf-8" ?>
<Window
x:Class="MapLocationDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Map Location Demo">
<Window.SystemBackdrop>
<MicaBackdrop />
</Window.SystemBackdrop>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TitleBar Title="Map Location Demo" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Margin="16" RowSpacing="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Location Demo" FontSize="20" FontWeight="Bold"/>
<StackPanel Grid.Row="1" Spacing="8">
<Button x:Name="FindMeButton" Content="Find My Location"
Click="FindMeButton_Click" HorizontalAlignment="Stretch"/>
<Button x:Name="AddGeofenceButton" Content="Add Geofence Here"
Click="AddGeofenceButton_Click" HorizontalAlignment="Stretch"/>
</StackPanel>
<TextBlock x:Name="StatusText" Grid.Row="2"
Text="Click 'Find My Location' to begin." TextWrapping="Wrap"/>
<ListView x:Name="EventLog" Grid.Row="3" Header="Event Log"/>
</Grid>
<Grid Grid.Column="1">
<MapControl x:Name="MyMap" />
<StackPanel x:Name="MapKeyMissing" Visibility="Collapsed"
HorizontalAlignment="Center" VerticalAlignment="Center"
Spacing="8">
<FontIcon Glyph="" FontSize="48"
HorizontalAlignment="Center"
Foreground="{ThemeResource SystemFillColorCautionBrush}" />
<TextBlock Text="Azure Maps key not configured"
FontSize="18" FontWeight="SemiBold"
HorizontalAlignment="Center" />
<TextBlock x:Name="MapKeyHint" TextWrapping="Wrap" MaxWidth="400"
HorizontalAlignment="Center" TextAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</StackPanel>
</Grid>
</Grid>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Devices.Geolocation;
using Windows.Devices.Geolocation.Geofencing;
namespace MapLocationDemo;
public sealed partial class MainWindow : Window
{
private Geolocator? _geolocator;
private BasicGeoposition _lastPosition;
private bool _mapAvailable;
public MainWindow()
{
InitializeComponent();
_mapAvailable = TryConfigureMap();
GeofenceMonitor.Current.GeofenceStateChanged += OnGeofenceStateChanged;
}
// Read the Azure Maps key from an environment variable.
// If missing, collapse the map and show an informational overlay.
private bool TryConfigureMap()
{
var key = Environment.GetEnvironmentVariable("AZURE_MAPS_KEY");
if (string.IsNullOrWhiteSpace(key))
{
MyMap.Visibility = Visibility.Collapsed;
MapKeyMissing.Visibility = Visibility.Visible;
MapKeyHint.Text = "Set the AZURE_MAPS_KEY environment variable "
+ "and restart.\nGeolocation and geofencing still work "
+ "without the map.";
Log("Azure Maps key not found — map disabled");
return false;
}
MyMap.MapServiceToken = key;
return true;
}
private async void FindMeButton_Click(object sender, RoutedEventArgs e)
{
FindMeButton.IsEnabled = false;
StatusText.Text = "Requesting location access...";
var access = await Geolocator.RequestAccessAsync();
if (access != GeolocationAccessStatus.Allowed)
{
StatusText.Text =
"Location access denied. Check Settings > Privacy > Location.";
FindMeButton.IsEnabled = true;
return;
}
_geolocator = new Geolocator { DesiredAccuracyInMeters = 100 };
try
{
var pos = await _geolocator.GetGeopositionAsync();
var lat = pos.Coordinate.Point.Position.Latitude;
var lon = pos.Coordinate.Point.Position.Longitude;
_lastPosition = new BasicGeoposition
{
Latitude = lat, Longitude = lon
};
StatusText.Text =
$"Location: {lat:F5}, {lon:F5} ({pos.Coordinate.Accuracy:F0} m)";
Log($"Position: {lat:F5}, {lon:F5}");
if (_mapAvailable)
{
var pt = new Geopoint(_lastPosition);
MyMap.Center = pt;
MyMap.ZoomLevel = 15;
var layer = new MapElementsLayer();
layer.MapElements = new List<MapElement>
{
new MapIcon { Location = pt }
};
MyMap.Layers.Clear();
MyMap.Layers.Add(layer);
}
}
catch (Exception ex) { StatusText.Text = $"Error: {ex.Message}"; }
finally { FindMeButton.IsEnabled = true; }
}
private void AddGeofenceButton_Click(object sender, RoutedEventArgs e)
{
if (_lastPosition.Latitude == 0 && _lastPosition.Longitude == 0)
{
StatusText.Text = "Get your location first.";
return;
}
var fence = new Geofence("MyGeofence",
new Geocircle(_lastPosition, 200),
MonitoredGeofenceStates.Entered | MonitoredGeofenceStates.Exited,
false, TimeSpan.FromSeconds(5));
GeofenceMonitor.Current.Geofences.Add(fence);
StatusText.Text = $"Geofence added at "
+ $"{_lastPosition.Latitude:F5}, {_lastPosition.Longitude:F5}";
Log("Geofence registered");
}
private void OnGeofenceStateChanged(
GeofenceMonitor sender, object args)
{
var reports = sender.ReadReports();
DispatcherQueue.TryEnqueue(() =>
{
foreach (var r in reports)
{
var msg = r.NewState switch
{
GeofenceState.Entered => $"Entered: {r.Geofence.Id}",
GeofenceState.Exited => $"Exited: {r.Geofence.Id}",
GeofenceState.Removed => $"Removed: {r.Geofence.Id}",
_ => null
};
if (msg != null) { StatusText.Text = msg; Log(msg); }
}
});
}
private void Log(string msg) =>
EventLog.Items.Insert(0, $"[{DateTime.Now:HH:mm:ss}] {msg}");
}
關鍵模式
-
MapControl內建於 Windows 應用程式 SDK 1.6 及更新版本中。 設定MapServiceToken為你的 Azure 地圖服務 鍵。 -
TryConfigureMap啟動時檢查AZURE_MAPS_KEY環境變數。 如果變數是空的,地圖會崩潰,並有覆蓋層說明如何修復——不會當機,也不會出現空白地圖。 -
DeviceCapability Name="location"在Package.appxmanifest中是必需或Geolocator.RequestAccessAsync返回Denied。 -
GeofenceMonitor.GeofenceStateChanged會在背景執行緒觸發,所以用DispatcherQueue.TryEnqueue來更新 UI。