本文基於Vicky&James 2024年10月22日在韓國Microsoft總部BMW meetup會議上的演講內容重新整理而成。這次研討會我們深入探討了基於XAML的各種平台、跨平台策略以及為有效的專案架構設計所需的核心技術。
介紹
大家好,我們中韓Microsoft MVP夫妻Vicky&James。我們從WPF開始就對包括Uno Platform在內的基於XAML的框架和專案架構設計有著深入的興趣與經驗。大家可以在我們的GitHub倉庫中查看和下載各種專案的原始碼:GitHub - jamesnetgroup

目錄
- XAML平台與跨平台概述
- 考量跨平台的.NET版本選擇策略
- View與ViewModel的連接策略分析
- 框架設計的必要功能及實作方案
- 在其他平台上有效使用WPF技術的核心策略
- 分散式專案管理的Bootstrapper設計方法論
- 在桌面跨平台中最大化利用WPF技術的策略
1. XAML平台與跨平台概述
XAML是一種用於以宣告方式定義UI的標記語言,被用於多個平台。XAML具有由物件(即類別)組成的階層結構,使開發人員能夠以物件導向的方式設計與管理UI。由於這種結構,開發人員直接處理XAML是很自然的。
當WPF首次出現時,它強調了開發人員與設計師之間的協作,但實際上XAML領域通常也由開發人員負責。這是因為XAML不僅僅是簡單的設計,而是形成了基於物件的階層結構,在複雜的自訂控制項實作中也發揮著重要作用。這種面向開發人員的設計方法促使XAML不僅在WPF中,而且在隨後出現的許多平台中都成為核心元件。
特別是,WPF對所有基於XAML的平台都產生了重大影響,並成為這些平台最重要的參考。
1.1 主要的XAML框架
- WPF:用於Windows桌面應用程式開發的強大框架,提供豐富的UI與圖形功能。
- Silverlight:用於在web瀏覽器中執行的網際網路應用程式的平台,目前已停止支援。它是WPF的輕量級版本,以插件方式執行。由於Web標準政策的變化,基於插件的Web平台消失,在Silverlight 2中引入了VisualStateManager(VSM) 來彌補Trigger的缺點。
- Xamarin.Forms:支援iOS、Android與Windows的行動應用開發平台。作為基於Mono的第一個基於XAML的跨平台,被Microsoft策略性收購並成為.NET Core的基礎。
- UWP(Universal Windows Platform):用於開發執行在Windows 10及以上版本的應用程式的平台。需要註冊Microsoft Store,並有WinAPI使用限制等約束。支援與WPF相同的自訂控制項設計。
- WinUI 3:Windows原生UI框架,是最新Windows應用開發的下一代UI平台。繼承了UWP的所有優點,同時解決了其限制,並採納了WPF的可擴展性。
- MAUI(.NET Multi-platform App UI):從.NET 6開始引入的跨平台UI框架,可以在單一專案中開發行動與桌面應用。
- Uno Platform:允許在各種平台上使用UWP與WinUI的API的框架,支援Web(WebAssembly)、行動與桌面。支援幾乎所有平台,並提供與WPF相同的自訂控制項設計。
- Avalonia UI:允許在跨平台上使用WPF風格XAML的開源UI框架。支援與WPF相同的自訂控制項設計,透過獨特的技術擴展支援各種平台。
- OpenSilver:為將舊的Silverlight遷移到OpenSilver而優化的開源平台。以與Silverlight幾乎相同的方式執行,也為WPF開發人員提供熟悉的環境。
2. 考量跨平台的.NET版本選擇策略
在跨平台應用程式開發中,需要謹慎選擇要使用的.NET版本。因為這將直接影響相容性、功能性與目標平台支援。
2.1 .NET版本選項
- .NET Framework:僅用於Windows,主要用於現有的WPF與WinForms應用程式。
- .NET Standard 2.0 / 2.1:為提供各種.NET實作之間的相容性而設計的標準。
- .NET (Core) 3.0及以上:支援Windows、macOS、Linux的跨平台.NET實作,包含最新功能與效能改進。
2.2 選擇標準與考量因素
如果需要跨平台支援,應該選擇.NET Core或最新的.NET。如果與現有.NET Framework函式庫或套件的相容性很重要,那麼則應該使用.NET Standard 2.0。如果想要最新功能與效能改進,就需要考慮.NET 5及以上版本。
此外,跨平台框架從.NET 5.0開始考慮相容性,並且基於最新版本持續進行功能改進,因此建議大家選擇最新的.NET版本。
策略建議:
- 將通用函式庫編寫為.NET Standard 2.0以確保最大相容性。
- 為每個平台建立專案並引用通用函式庫。
- 如果可能,使用.NET 6及以上版本以獲得最新功能與效能改進。
3. View與ViewModel的連接策略分析
在MVVM模式中,View與ViewModel的連接是核心部分。連接方式的不同會導致使用MVVM的方式完全不同。因此,我們需要根據使用MVVM的目的來決定DataContext分配方式。
3.1 傳統的DataContext直接分配方式
在程式碼後置建立ViewModel並直接分配給View的DataContext。
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
優點:
- 實作簡單直觀
- 可以明確控制檢視模型的建立時機
- 可以向建構函式傳遞所需參數
缺點:
- View與ViewModel之間產生強耦合
- 單元測試時難以模擬(Mock)ViewModel
- 難以利用相依性注入(DI)
- 需要直接指定DataContext分配時機,可能難以保持一致性
3.2 在XAML中建立ViewModel實例
在XAML中設定DataContext來實例化ViewModel。
<Window x:Class="MyApp.MainWindow"
xmlns:local="clr-namespace:MyApp.ViewModels">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<!-- Window content -->
</Window>
優點:
- XAML的智慧感知支援可以減少繫結錯誤
- 可以在設計器中預覽實際的資料繫結
- 明確表達View與ViewModel之間的關係
缺點:
- ViewModel建立時難以進行相依性注入
- 複雜的初始化邏輯或參數傳遞受限
- DataContext分配時機被強制化,降低了靈活性
3.3 ViewModel直接建立及相依傳遞
在程式碼後置建立ViewModel時直接傳遞所需的相依。
public MainWindow()
{
InitializeComponent();
var dataService = new DataService();
var loggingService = new LoggingService();
DataContext = new MainViewModel(dataService, loggingService);
}
優點:
- 可以在建立ViewModel時明確傳遞所需的相依
- 可以實作複雜的初始化邏輯
- 可以根據Runtime靈活建立ViewModel實例
缺點:
- View需要了解ViewModel的相依關係
- 隨著相依增加,程式碼後置會變得複雜
- View與ViewModel之間的耦合度仍然很高
- 需要直接指定DataContext分配時機,可能難以保持一致性
- 由於不使用DI管理專案,可能會出現相依關係混亂的情況
3.4 善用相依性注入(DI)容器
使用DI容器來管理ViewModel及其相依可以降低View與ViewModel之間的耦合度。
public MainWindow()
{
InitializeComponent();
DataContext = ServiceProvider.GetService<MainViewModel>();
}
優點:
- 降低View與ViewModel之間的耦合度
- 相依關係集中管理,提高可維護性
- 便於進行單元測試
- 可以在執行時靈活更改相依關係
缺點:
- DI容器的初始配置可能較為複雜
- 團隊成員需要理解DI模式
- 仍然需要直接在DataContext中建立檢視模型,分配時機的一致性可能難以保持
- 需要決定是將檢視模型作為單例還是實例來管理,並考慮檢視的生命週期。需要制定明確的規則並嚴格遵守
3.5 View的自動ViewModel建立策略
為了解決上述問題,我們可以考慮在View建立的約定時間點,透過相依性注入建立ViewModel並且分配給DataContext。例如,設計一個基於ContentControl的View,自動建立ViewModel就是一個有效的方法。
public class UnoView : ContentControl
{
public UnoView()
{
this.Loaded += (s, e) =>
{
var viewModelType = ViewModelLocator.GetViewModelType(this.GetType());
DataContext = ServiceProvider.GetService(viewModelType);
};
}
}
優點:
- 可以保持DataContext分配時機的一致性
- 降低View與ViewModel之間的耦合度
- ViewModel的建立與相依性注入自動處理
- View不需要知道自己應該使用哪個ViewModel
這幾乎是一個沒有缺點的方法,透過管理單一的View,可以統一處理時機與處理邏輯。在結構性完善與擴展方面也能保證很好的設計。
不過需要View與ViewModel之間的Mapping,可以使用Dictionary或Mapping Table來實現。這樣可以集中管理View與ViewModel之間的連接資訊。關於連接管理的映射方法,我們將在後面的Bootstrapper設計方法論中詳細討論。
4. 框架設計的必要功能及實作方案
在設計應用程式架構時,建構考慮可重用性與可擴展性的框架非常重要。為此,使用相依性注入(DI)容器是必不可少的。
4.1 相依性注入(DI)容器的使用
DI是現代軟體開發中不可或缺的模式,對相依關係管理與降低耦合度有很大幫助。然而,在像WPF這樣的桌面應用程式中,Web應用程式中常用的Microsoft.Extensions.DependencyInjection等DI容器可能並不完全適合。
4.1.1 Microsoft.Extensions.DependencyInjection的使用與注意事項
Microsoft.Extensions.DependencyInjection是.NET官方提供的DI容器,可以說是.NET Foundation的標準。它被用於ASP.NET Core、EntityFrameworkCore、MAUI等各種平台與框架中的幾乎所有系統中使用,並提供Transient、Scoped、Singleton等生命週期管理功能。
然而,在WPF中,這種標準DI的生命週期可能與WPF實際情況並不完全匹配。
注意事項:
- 在WPF等桌面應用程式中,可能不需要
Scoped生命週期 Transient或Singleton的概念是為服務或Web應用程式設計的,在WPF中某些功能可能不適用- 可能帶來不必要的複雜性,對於WPF的使用場景來說,更簡單輕量的DI容器可能更合適
當然,即使不使用Transient這樣的生命週期也可以使用DI,但準確理解這些要點是非常重要的。
4.1.2 CommunityToolkit.Mvvm的DI
CommunityToolkit.Mvvm並不直接提供像Microsoft.Extensions.DependencyInjection這樣的DI。這可能是因為Microsoft.Extensions.DependencyInjection與WPF的生命週期特性不完全匹配。
但是,CommunityToolkit.Mvvm透過提供Ioc.Default允許開發者使用任何想要的DI容器。任何實作了System.IServiceProvider介面的DI容器都可以註冊使用。
因此,使用CommunityToolkit.Mvvm時可以選擇DI。最常用的DI之一無疑是Microsoft.Extensions.DependencyInjection,使用Prism這樣的DI也是非常有效的組合。
4.1.3 直接設計DI容器的優勢
基於IServiceProvider介面設計DI的方法可以註冊到CommunityToolkit.Mvvm的Ioc.Default中,實現內部功能的連接與相容。由於IServiceProvider只要求實作GetService等最基本的功能,因此可以實現非常簡單的DI。
優點:
- 實作只包含必要功能的簡單DI容器,降低專案複雜性
- 可以在內部設計、控制與擴展各種功能
- 可以精確建構整體框架架構與專案設計
- 提供不依賴特定平台的統一DI容器,有利於跨平台開發
範例程式碼:
// 基於IServiceProvider的DI容器實作
public class SimpleServiceProvider : IServiceProvider
{
private readonly Dictionary<Type, Func<object>> _services = new();
public void AddService<TService>(Func<TService> implementationFactory)
{
_services[typeof(TService)] = () => implementationFactory();
}
public object GetService(Type serviceType)
{
return _services.TryGetValue(serviceType, out var factory) ? factory() : null;
}
}
// DI容器註冊與使用
var serviceProvider = new SimpleServiceProvider();
serviceProvider.AddService<IMainViewModel>(() => new MainViewModel());
Ioc.Default.ConfigureServices(serviceProvider);
這樣基於IServiceProvider介面實現簡單的DI容器,就可以註冊到CommunityToolkit.Mvvm的Ioc.Default中,實現內部功能的連接與相容。如果覺得使用Microsoft.Extensions.DependencyInjection、Prism等主流DI太繁重,自己直接來實現一個是非常有吸引力的選擇。
注意:
如果不遵循IServiceProvider等System.ComponentModel標準,可能會失去與CommunityToolkit.Mvvm的Ioc相容性。但是我們可以將CommunityToolkit.Mvvm僅作為MVVM相關的模組,建立一個更專業、更統一的、不依賴特定平台或框架的DI容器。這對於建立可以在跨平台等多個XAML平台上通用的框架是非常適合的。
5. WPF技術在其他平台上的有效使用策略
要在其他XAML平台上最大限度地利用WPF強大的功能,我們需要了解一些歷史背景與核心策略。同時也需要詳細了解可以直接使用WPF技術的平台特性。
5.1 平台間的特徵與差異理解
UWP與WinUI 3的差異:UWP作為Windows 10的專用平台,由於應用商店註冊指南與WinAPI限制等原因,與WPF和WinForms等傳統平台的相容性較差。因此,WinUI 3應運而生,它不僅繼承了UWP的所有優點,還改進了其存在的問題,發展成為了一個像WPF一樣具有高自由度的平台。
Uno Platform桌面版與WinUI 3的一致性:Uno Platform支援Windows、macOS、Linux的桌面平台完全遵循WinUI 3的方式。因此,WinUI 3直接使用UWP的核心庫,而Uno Platform也同樣採用WinUI 3的方式,這意味著所有以
Microsoft.*開頭的DLL庫都可以共享使用。
理解這些平台間的特徵可以讓我們認識到Uno Platform Desktop是一個非常高效且具有吸引力的平台。因此,在WPF與Uno Platform之間進行技術共享與轉換的策略非常有效且高效,因為它們與WinUI 3和UWP都有著緊密的聯繫。
5.2 充分利用VisualStateManager(VSM)
由於不是所有平台都可以直接使用WPF的Trigger,所以我們就需要一個替代策略。VisualStateManager(VSM)在解決這個問題上起著核心作用。
VSM是在Silverlight 2.0中引入的,用來彌補Trigger不足的,對自訂控制項與XAML之間的狀態處理進行了最佳化。隨後在.NET 4.0中,VSM也被引入到WPF中,WPF的Button、CheckBox、DataGrid、Window等所有CustomControl的內部設計都從Trigger改為了VSM。
優點:
- 在不能直接使用Trigger的平台上可以透過VSM實現相同功能
- 可以有效實現UI狀態管理與動畫
- 可以透過VSM統一不同平台的不同行為
最終,透過集中使用VSM,就可以實現在WPF、Uno Platform Desktop、WinUI 3、UWP等平台上建構相同的XAML與CustomControl,原始碼也可以完全共享。
5.3 靈活運用IValueConverter
IValueConverter是一個允許在資料繫結時轉換值的介面,對於抽象化平台間差異非常有用。
策略性使用:
- 可以實現和替代幾乎與Trigger相同的功能,編寫簡單有效的原始碼
- 由於每次都需要建立Converter,而且重用性標準模糊,建議不要過分追求重用性,而是靈活使用
- 即使沒有重用性也要直觀使用,重要的是透過明確的命名來盡量減少分支,專門化使用
局限與補充:
- 僅使用
IValueConverter是有限的 IValueConverter應用於簡單轉換,複雜場景的管理會帶來負擔,這時我們應透過VSM解決- 複雜的狀態處理最好使用
VisualStateManager
總之,IValueConverter補充了VSM的不足,對於簡單直接的轉換工作應該直觀靈活使用,不要過分追求重用性。
6. 分散式專案管理的Bootstrapper設計方法論
隨著應用程式變得複雜與模組化,初始化過程與相依管理變得越來越重要。Bootstrapper模式在集中管理這些初始化邏輯方面非常有用。
雖然所有平台都以Application設計為基礎,但由於各平台的特特性與方式不同,Application設計各不相同。因此,為了在所有平台上保持相同的開發方式,使用Bootstrapper結構設計是非常有效的。
6.1 Bootstrapper的作用與必要性
Bootstrapper的功能:
- 相依性注入設定:初始化DI容器,註冊必要的服務、View與ViewModel。
- 管理檢視與檢視模型的連接:透過相依性注入註冊View,管理View與ViewModel之間的映射。
- 集中化配置管理:所有配置都在Bootstrapper中管理,使應用程式專案只執行其角色,其餘功能實現透過專案分散式與模組化來管理。
- 此外,還可以靈活地擴展集中化管理項目,且不會影響Application。
優點:
- 透過分離應用程式的初始化邏輯來提高程式碼的可讀性與可維護性。
- 透過專案分散式與模組化,可以獨立開發功能實現。
- 最小化平台之間的結構差異,保持一致的架構。
6.2 Bootstrapper的設計方案
範例程式碼:
namespace Jamesnet.Core;
public abstract class AppBootstrapper
{
protected readonly IContainer Container;
protected readonly ILayerManager Layer;
protected readonly IViewModelMapper ViewModelMapper;
protected AppBootstrapper()
{
Container = new Container();
Layer = new LayerManager();
ViewModelMapper = new ViewModelMapper();
ContainerProvider.SetContainer(Container);
ConfigureContainer();
}
protected virtual void ConfigureContainer()
{
Container.RegisterInstance<IContainer>(Container);
Container.RegisterInstance<ILayerManager>(Layer);
Container.RegisterInstance<IViewModelMapper>(ViewModelMapper);
Container.RegisterSingleton<IViewModelInitializer, DefaultViewModelInitializer>();
}
protected abstract void RegisterViewModels();
protected abstract void RegisterDependencies();
public void Run()
{
RegisterViewModels();
RegisterDependencies();
OnStartup();
}
protected abstract void OnStartup();
}
透過這樣的抽象化,可以明確強調管理結構,並透過虛方法控制時間點與順序。這有助於靈活擴展與完善,並且不影響Application,使其在各種平台上以一致的方式執行。
7. 在跨平台桌面環境中最大化WPF技術的策略
透過在其他基於XAML的跨平台框架中最大限度地利用WPF中使用的技術與模式,可以提高開發效率。
7.1 實作可在所有平台上執行的框架

Jamesnet.Core是基於.NET Standard 2.0的框架,允許在WPF、Uno Platform與WinUI 3中實現相同的專案設計。該框架具有以下特點:
- DI設計:利用基於IServiceProvider的DI容器,可以與CommunityToolkit.Mvvm配合使用。
- MVVM Bootstrapper:集中管理專案的初始化與相依性注入。
- View與ViewModel之間的連接管理:透過層管理等方式降低View與ViewModel的耦合度。
- 在所有基於XAML的平台上統一執行。
- 直接引用儲存庫原始碼,便於偵錯、功能實現、擴展與研究。
優點:
- 無論使用WPF、Uno Platform還是WinUI 3開發,都可以保持相同的架構。
- 使用
Uno Platform Desktop可以在macOS與Linux上進行開發與執行。 - 可以使用
JetBrains Rider建構跨平台開發環境。
7.2 實際實作案例分析
英雄聯盟客戶端重構專案利用Jamesnet.Core框架,在WPF、Uno Platform與WinUI 3等不同平台上使用相同的程式碼庫與架構實現。




- WPF版本:GitHub - leagueoflegends-wpf
- Uno Platform版本:GitHub - leagueoflegends-uno
- WinUI 3版本:GitHub - leagueoflegends-winui3
策略方法:
- 透過Jamesnet.Core框架保持專案設計的統一性。
- 利用DI容器與Bootstrapper集中管理檢視與檢視模型。
- 使用
VisualStateManager(VSM)替代Trigger,在不同平台上以相同方式管理UI狀態。
成果:
- 97%以上的程式碼共享,最大化了向其他平台擴展的可能性。
- 在各種平台上提供一致的使用者體驗與開發方法論,使技術轉換更容易。
- 透過專案分散化、模組化與管理集中化,大大降低了開發與維護成本。
- 透過CustomControl的模組化開發提高了重構與擴展性,在GPT、Claude等AI應用方面,分散的檢視系統也更加有效。
結論
WPF技術與模式仍然強大,將其應用於跨平台開發可以提高開發效率與程式碼重用性。特別是使用Jamesnet.Core框架,透過DI容器的集中化管理策略與引入Bootstrapper,有助於降低檢視與檢視模型之間的耦合度,提高可維護性。
此外,透過積極使用VisualStateManager與IValueConverter,可以最小化平台之間的差異並保持一致的設計。透過這些策略,可以超越WPF基礎,策略性地實現跨平台技術擴展。
特別是,UWP、WinUI 3與Uno Platform之間100%相同地使用XAML相關DLL,因此這些平台之間幾乎沒有差異。因此,對WPF開發者來說,使用Uno Platform桌面版非常有效且具有策略意義。這是因為從WPF轉換到Uno可以在幾小時內完成,轉換到WinUI 3也非常容易。
未來,WPF技術與基於XAML的框架將繼續發展,利用這些進行跨平台開發將變得更加重要。開發人員需要很好地把握這些趨勢,制定適當的策略,開發高品質的應用程式。
參考
主要倉庫
- Jamesnet.Core框架:GitHub - jamesnet.core
- 在所有基於XAML的平台上執行的框架,提供DI、MVVM、Bootstrapper等功能。
- 英雄聯盟客戶端重構專案:
- WPF版本:GitHub - leagueoflegends-wpf
- Uno Platform版本:GitHub - leagueoflegends-uno
- WinUI 3版本:GitHub - leagueoflegends-winui3
目前已更新的WPF教學(自訂控制項)
