將 UWP 應用程式遷移到 WinUI 3

UWP 已不再積極開發。 WinUI 3 和 Windows 應用程式 SDK 是它的後繼者——而 AI 工具可以自動化大部分的遷移工作。 主要挑戰是 AI 模型是用多年 UWP 樣本訓練的,沒有指導,它們會重現你想擺脫的模式。 這個頁面會提供你的代理程式所需的脈絡資訊,讓它能正確處理。

安裝 WinUI 代理插件

該 winui-uwp-migration 技能會自動處理常見的替換:

gh copilot plugin install winui@awesome-copilot

完整細節請參見 WinUI 代理插件 。

API 替換表

以下表格總結了最常見的 API 替換方式。 完整詳細的映射——包括成員、屬性及較少見的 API——請參見「將 UWP API 與函式庫映射至 Windows 應用程式 SDK」。

Important

x:Bind 預設為 OneTime 模式。 與(預設為 {Binding})OneWay不同x:Bind,除非你指定Mode=OneWay或Mode=TwoWay,否則只會評估一次。 遷移時,審核所有 x:Bind 綁定到執行時會變動屬性的表達式——缺少 Mode=OneWay 會導致「UI 不更新」的錯誤,而這些錯誤在編譯時是看不見的。

Namespaces

UWP WinUI 3
Windows.UI.Xaml.* Microsoft.UI.Xaml.*
Windows.UI.Xaml.Controls.* Microsoft.UI.Xaml.Controls.*
Windows.UI.Xaml.Media.* Microsoft.UI.Xaml.Media.*
Windows.UI.Composition Microsoft.UI.Composition

線程

UWP WinUI 3
CoreDispatcher DispatcherQueue
Dispatcher.RunAsync(...) DispatcherQueue.TryEnqueue(...)
CoreApplication.MainView.CoreWindow.Dispatcher this.DispatcherQueue (來自 a Window 或 Page)

Windowing

UWP WinUI 3
ApplicationView AppWindow
ApplicationView.GetForCurrentView() AppWindow.GetFromWindowId(...)
ApplicationViewTitleBar AppWindowTitleBar
CoreWindow Microsoft.UI.Xaml.Window
SystemNavigationManager 透過 AppWindowTitleBar 返回鍵

對話與選擇器

UWP WinUI 3
MessageDialog ContentDialog(設定 XamlRoot)
FileOpenPicker FileOpenPicker + InitializeWithWindow
FileSavePicker FileSavePicker + InitializeWithWindow
FolderPicker FolderPicker + InitializeWithWindow

Important

選擇器在呼叫InitializeWithWindow(或類似函式)之前需要PickSingleFileAsync:

var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);

ContentDialog 需要 XamlRoot (非 InitializeWithWindow):

var dialog = new ContentDialog { XamlRoot = this.Content.XamlRoot, ... };
await dialog.ShowAsync();

Notifications

UWP WinUI 3
Windows.UI.Notifications.ToastNotificationManager Microsoft.Windows.AppNotifications.AppNotificationManager
Windows.UI.Notifications.BadgeUpdateManager Microsoft.Windows.BadgeNotifications.BadgeNotificationManager
Windows.UI.Notifications.TileUpdateManager 磁磚已被棄用——請使用通知或小工具

網路與 HTTP

UWP WinUI 3(推薦)
Windows.Web.Http.HttpClient System.Net.Http.HttpClient (攜帶型,無 WinRT 依賴)
Windows.Web.Syndication.SyndicationClient System.ServiceModel.Syndication.SyndicationFeed + HttpClient
Windows.Web.AtomPub.AtomPubClient System.ServiceModel.Syndication 或直接 HTTP 連線

Note

WinRT 的 HTTP API (Windows.Web.Http) 仍然能在封裝的 WinUI 3 應用程式中使用,但建議使用 .NET 等效物,以提升可攜性、更簡單的除錯,以及更廣泛的生態系統支援(中介軟體、DI、模擬)。

UWP WinUI 3
Frame.Navigate(typeof(MyPage)) Frame.Navigate(typeof(MyPage)) — 未曾改變
SystemNavigationManager.BackRequested 透過 NavigationView 或 AppWindow 處理
Windows.UI.Core.Preview.SystemNavigationManagerPreview AppWindow.Closing 事件

Note

自訂漢堡式導覽(SplitView + NavMenuListView):許多 UWP 範例使用自訂的 AppShell.xaml 搭配 SplitView,以及自行撰寫的 NavMenuListView 控制項(約 500 多行)來實作導覽。 在 WinUI 3 中,將整個模式替換為 NavigationView,提供相同的使用者體驗,並內建無障礙功能、響應式行為及返回按鈕支援。 這通常是 80% 的程式碼縮減。

MVVM 模式

UWP(常見自訂實作) WinUI 3(推薦)
自訂 BindableBase / ObservableObject CommunityToolkit.Mvvm.ComponentModel.ObservableObject
自訂 DelegateCommand / RelayCommand CommunityToolkit.Mvvm.Input.RelayCommand
手動 SetProperty + OnPropertyChanged [ObservableProperty] 源產生器
自訂 INavigationService 內建 Frame.Navigate + NavigationView

Tip

CommunityToolkit.Mvvm NuGet 套件是 WinUI 3 應用程式推薦的 MVVM 基礎。 它以經過測試、由原始碼產生的對應實作取代手寫的基底類別,省去數百行樣板程式碼。

dotnet add package CommunityToolkit.Mvvm

應用程式週期

UWP WinUI 3
Application.Current.Suspending Microsoft.Windows.AppLifecycle(需架構變更 — 詳見註解)
Application.Current.Resuming AppInstance.GetCurrent().Activated (見註解)
BackgroundTaskBuilder Windows 應用程式 SDK背景任務

Note

WinUI 3 應用程式生命週期遷移並非簡單的 API 名稱交換。 Windows 應用程式 SDK 採用不同的啟用與暫停模式。 將生命週期程式碼視為需要專用重寫,而非自動替換。 完整模型請參閱 Windows 應用程式 SDK 生命週期文件。

設定與儲存

UWP WinUI 3(打包版) WinUI 3(未封裝)
ApplicationData.Current.LocalSettings 未更改 ❌ 拋出 — 無封裝身份
ApplicationData.Current.LocalFolder 未更改 ❌ 拋出 — 無封裝身份
Windows.Storage.KnownFolders 未更改 ❌ 拋出 — 無封裝身份

Warning

未封裝的應用程式 無法使用 ApplicationData.Current ——因為沒有套件身份,執行時會丟出。 改用標準的 .NET 檔案 API:

var appData = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
    "YourAppName");
Directory.CreateDirectory(appData);
var json = JsonSerializer.Serialize(data);
await File.WriteAllTextAsync(Path.Combine(appData, "settings.json"), json);

Note

如果你的 UWP 應用程式曾使用 DataContractSerializer[DataMember]/[IgnoreDataMember],考慮遷移到System.Text.Json(更快、規模更小、原始碼生成支援)。 屬性映射為:

  • [DataMember] → [JsonPropertyName("name")] (或直接使用房產名稱)
  • [IgnoreDataMember] → [JsonIgnore]
  • [DataContract] → 不需要等效的(System.Text.Json 預設會序列化公開屬性)

不變的 API

Windows.Devices.*、Windows.Media.*、Windows.UI.ViewManagement.UISettings、Windows.UI.Color,以及大多數 XAML 命名空間外的 WinRT API 均未改變。

無直接對應的控制項

有些 UWP 控制項在 WinUI 3 裡並不存在。 根據你的情況選擇替換方案:

UWP 控制項 WinUI 3 替代方案 註釋
Pivot TabView、NavigationView(頂端模式)或 RadioButtons + 可見性 對於 2–3 個固定分頁,使用 RadioButtons 搭配可見性切換最簡單。 對於動態/可關閉的分頁,請使用 TabView。
RadialController WinRT 與視窗柄的互通 RadialController.CreateForCurrentView() 沒有直接對應項目。 RadialControllerInterop 與 GetForWindow(hwnd) 一起使用。
SystemNavigationManager 自訂返回按鈕或 NavigationView.IsBackEnabled SystemNavigationManager.GetForCurrentView() 不存在。 新增自己的返回按鈕,或使用 NavigationView內建的返回按鈕。

墨水、Win2D 與印刷

這些子系統需要特定的遷移步驟,超越命名空間的變更。

Windows Ink

WinUI 包含 InkCanvas、 InkToolbar、 InkPresenter 作為 Windows 應用程式 SDK 實驗版本中的實驗性 API。 穩定版不包含這些項目。 關於設定、工具列自訂及已知限制,請參見 WinUI 應用程式的手寫筆跡控制項。

在實驗性版本中,控制項和處理墨水輸入的類型位於 Microsoft.UI.Xaml.Controls 中。 筆劃與繪畫屬性保留於 Windows.UI.Input.Inking:

UWP WinUI 3(實驗性版本)
Windows.UI.Xaml.Controls.InkCanvas、 InkToolbar,以及其他 InkToolbar* 類型 Microsoft.UI.Xaml.Controls,相同類型名稱。 部分成員意見不同。
Windows.UI.Input.Inking.InkPresenter、InkStrokeContainer、InkStrokeInput、InkUnprocessedInput、InkSynchronizer、InkInputConfiguration、InkInputProcessingConfiguration、InkInputProcessingMode、InkInputRightDragAction、InkHighContrastAdjustment、InkStrokesCollectedEventArgs、InkStrokesErasedEventArgs Microsoft.UI.Xaml.Controls,相同的類型名稱。 部分成員意見不同。
Windows.UI.Input.Inking.InkStroke、InkDrawingAttributes、InkPoint、InkStrokeBuilder、InkPersistenceFormat、PenTipShape 沒有變化
Windows.UI.Core.CoreInputDeviceTypes 沒有變化

前兩列中的每個 UWP 類型都有一個同名的對應類型,但 Windows 應用程式 SDK 2.5.4-experimental 並未包含其所有成員:

  • InkStrokeContainer 沒有公共建構子,且 InkPresenter.StrokeContainer 為唯讀。 要切換筆劃組,將每組筆劃儲存為 SaveAsync ,並用 LoadAsync還原 ,以 取代當前筆劃。
  • SaveAsync 並 LoadAsync 回傳 IAsyncAction,因此不會回報進度,也 SaveAsync 不會回傳它寫入的位元組數。
  • InkStrokeContainer.GetRecognitionResults、InkStrokeContainer.UpdateRecognitionResults和InkInputConfiguration.IsPenHapticFeedbackEnabled無法使用。
  • InkToolbarRulerButton.Ruler 和 InkToolbar.IsRulerButtonCheckedChanged 活動都無法使用。 UWP 已棄用InkToolbarRulerButton,改用InkToolbarStencilButton,所以請改用模板按鈕和InkToolbar.IsStencilButtonCheckedChanged事件。

Windows 應用程式 SDK 2.4.1 實驗性新增了橡皮擦 API,例如 InkToolbarEraserKind 和 InkToolbarEraserButton.SelectedEraser,而 2.5.4 實驗性移除了這些 API。 它們從未是 UWP 的一部分,所以不要把它們加到遷移的程式碼裡。 完整列表請參見 2.4.1 實驗變更。

因為像 InkPresenter 和 InkStrokeContainer 這類類型存在於兩個命名空間中,匯入 Microsoft.UI.Xaml.Controls 和 Windows.UI.Input.Inking 時使用這些名稱會產生錯誤 CS0104。 匯入 Microsoft.UI.Xaml.Controls,並為你使用的 Windows.UI.Input.Inking 類型新增別名指令:

using Microsoft.UI.Xaml.Controls;
using InkDrawingAttributes = Windows.UI.Input.Inking.InkDrawingAttributes;
using InkStroke = Windows.UI.Input.Inking.InkStroke;

InkStrokeContainer.SaveAsync 寫入 IOutputStream,並且 LoadAsync 從 IInputStream 讀取。 要使用 System.IO 檔案串流,請將其轉換為 AsOutputStream 和 AsInputStream。 檔案需要using System;來等候這些 Windows 執行階段 方法,以及 using System.IO; 和 using Windows.Storage.Streams;:

// Save ink strokes to a file.
using (FileStream fileStream = File.Create(filePath))
using (IOutputStream output = fileStream.AsOutputStream())
{
    await inkStrokeContainer.SaveAsync(output);
    await output.FlushAsync();
}

// Load ink strokes from a file.
using (FileStream fileStream = File.OpenRead(filePath))
using (IInputStream input = fileStream.AsInputStream())
{
    await inkStrokeContainer.LoadAsync(input);
}

你可以繼續將工具列連接到畫布 InkToolbar.TargetInkCanvas,或者將 TargetInkPresenter 設定為直接連接到一個 InkPresenter。 要將自己的工具加入工具列,請使用 InkToolbarCustomToolButton 和 InkToolbarCustomToggleButton,如「 自訂墨水工具列」中所示。 遷移前,請檢視 已知的限制,例如尺規和量角器範本已回報的問題。

如果你的應用程式必須使用穩定版本,就沒有內建的手寫筆跡控制項可遷移至。 用 Win2D 渲染筆觸,自己處理指標輸入,或使用第三方墨跡程式庫。 要取代 AppBarToggleButton,請使用具有 CommandBar 個項目的 InkToolbar。

Win2D

Win2D 套件名稱更改,但 API 表面相同:

UWP WinUI 3
Win2D.uwp (NuGet) Microsoft.Graphics.Win2D (NuGet)

所有 Microsoft.Graphics.Canvas.* API(CanvasDevice, CanvasBitmap, CanvasRenderTarget, DrawInk) 的運作方式都一樣。 只有 NuGet 套件參考需要更新。

列印

UWP 列印使用 PrintManager.GetForCurrentView()。 WinUI 3 需要視窗句柄互通:

// UWP
var printManager = PrintManager.GetForCurrentView();
printManager.PrintTaskRequested += OnPrintTaskRequested;
await PrintManager.ShowPrintUIAsync();

// WinUI 3 — must pass window handle
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
var printManager = PrintManagerInterop.GetForWindow(hwnd);
printManager.PrintTaskRequested += OnPrintTaskRequested;
await PrintManagerInterop.ShowPrintUIForWindowAsync(hwnd);

PrintDocument渲染 API(Paginate, GetPreviewPage, AddPages) 保持不變。

Important

若省略視窗把手,則 PrintManagerInterop.GetForWindow 會拋出 COMException。 這與 FileOpenPicker 的互通模式相同——任何在 UWP 中使用 GetForCurrentView() 的 API,在 WinUI 3 中都需要視窗控制代碼。

啟動提示

I'm migrating a UWP app to WinUI 3 using the Windows App SDK.

Apply these substitutions:
- Windows.UI.Xaml.* → Microsoft.UI.Xaml.*
- CoreDispatcher / Dispatcher.RunAsync → DispatcherQueue.TryEnqueue
- ApplicationView → AppWindow + AppWindowTitleBar
- CoreWindow → Microsoft.UI.Xaml.Window
- MessageDialog → ContentDialog (set XamlRoot, not InitializeWithWindow)
- FileOpenPicker / FileSavePicker / FolderPicker → add InitializeWithWindow
- Windows.UI.Notifications → Microsoft.Windows.AppNotifications
- SystemNavigationManager.BackRequested → NavigationView back handling
- Pivot → TabView, NavigationView (top mode), or RadioButtons (no direct equivalent)
- InkCanvas, InkToolbar, InkPresenter, InkStrokeContainer, and the other ink input types → Microsoft.UI.Xaml.Controls (experimental Windows App SDK releases only; if the project uses a stable release, flag them instead)
- Ink members that Windows App SDK 2.5.4-experimental doesn't include (new InkStrokeContainer(), assignments to InkPresenter.StrokeContainer, GetRecognitionResults, UpdateRecognitionResults, IsPenHapticFeedbackEnabled, InkToolbarRulerButton.Ruler, IsRulerButtonCheckedChanged, and progress or byte counts from SaveAsync and LoadAsync) → flag them
- PrintManager.GetForCurrentView → PrintManagerInterop.GetForWindow(hwnd)
- Win2D.uwp NuGet → Microsoft.Graphics.Win2D NuGet
- InkStroke, InkDrawingAttributes, InkPoint, InkStrokeBuilder → no change (Windows.UI.Input.Inking; use using aliases to avoid CS0104)

Do not use any Windows.UI.Xaml.* namespaces in new code.
Do not use CoreDispatcher — use DispatcherQueue.
Do not add InkToolbarEraserKind, SelectedEraser, or other eraser APIs from Windows App SDK 2.4.1-experimental. Version 2.5.4-experimental removed them.
x:Bind defaults to Mode=OneTime. Add Mode=OneWay for any binding that should update at runtime.
Flag any APIs without a direct WinUI 3 equivalent rather than guessing.

專案檔案變更

取代UWP目標框架:

<!-- Before (UWP) -->
<TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>

<!-- After (WinUI 3) -->
<TargetFramework>net10.0-windows10.0.19041.0</TargetFramework>
<WindowsSdkPackageVersion>10.0.19041.31</WindowsSdkPackageVersion>

新增 Windows 應用程式 SDK 套件:

dotnet add package Microsoft.WindowsAppSDK