本文基於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(model-view-viewmodel)模式中,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的veiw,自動創建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教程(自定義控制項)
