基於xaml框架和跨平台項目架構設計的深入技術分析

基於xaml框架和跨平台項目架構設計的深入技術分析

我們深入探討了基於xaml的各種平台、跨平台戰略以及為有效的項目架構設計所需的核心技術。

最后更新 2024/11/8 下午10:03
Vicky&James
预计阅读 20 分钟
分类
.NET
标签
.NET C# WPF 架構設計 跨平台

本文基於vicky&james 2024年10月22日在韓國microsoft總部bmw meetup會議上的演講內容重新整理而成。這次研討會我們深入探討了基於xaml的各種平台、跨平台戰略以及為有效的項目架構設計所需的核心技術。

居間

大家好,我们中韩Microsoft MVP夫妇Vicky&James。我们从WPF开始就对包括Uno Platform在内的基于XAML的框架和项目架构设计有着深入的兴趣和经验。大家可以在我们的GitHub仓库中查看和下载各种项目的源代码:GitHub - jamesnetgroup

目錄

  1. xaml平台和跨平台概述
  2. 考慮跨平台的.net版本選擇策略
  3. view和viewmodel的連接策略分析
  4. 框架設計的必要功能及實現方案
  5. 在其他平台上有效使用wpf技術的核心策略
  6. 分布式項目管理的bootstrapper設計方法論
  7. 在桌面跨平台中最大化利用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等各种平台和框架中的几乎所有系统中使用,并提供TransientScopedSingleton等生命周期管理功能。

然而,在wpf中,這種標準di的生命周期可能和wpf實際情況並不完全匹配。

注意事項:

  • 在WPF等桌面应用程序中,可能不需要Scoped生命周期
  • TransientSingleton的概念是为服务或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.MvvmIoc.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.MvvmIoc.Default中,实现内部功能的连接和兼容。如果觉得使用Microsoft.Extensions.DependencyInjectionPrism等主流DI太繁重,自己直接来实现一个是非常有吸引力的选择。

注意: 如果不遵循IServiceProviderSystem.ComponentModel标准,可能会失去和CommunityToolkit.MvvmIoc兼容性。但是我们可以将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等不同平台上使用相同的代碼庫和架構實現。

戰略方法:

  • 通過jamesnet.core框架保持項目設計的統一性
  • 利用di容器和bootstrapper集中管理視圖和視圖模型。
  • 使用VisualStateManager(VSM)替代Trigger,在不同平台上以相同方式管理UI状态。

成果:

  • 97%以上的代碼共享,最大化了向其他平台擴展的可能性。
  • 在各種平台上提供一致的用戶體驗和開發方法論,使技術轉換更容易。
  • 通過項目分散化、模塊化和管理集中化,大大降低了開發和維護成本。
  • 通過customcontrol的模塊化開發提高了重構和擴展性,在gpt、claude等ai應用方面,分散的視圖系統也更加有效。

結論

wpf技術和模式仍然強大,將其應用於跨平台開發可以提高開發效率和代碼重用性。特別是使用jamesnet.core框架,通過di容器的集中化管理策略和引入bootstrapper,有助於降低視圖和視圖模型之間的耦合度,提高可維護性。

此外,通過積極使用visualstatemanagerivalueconverter,可以最小化平台之間的差異並保持一致的設計。通過這些策略,可以超越wpf基礎,戰略性地實現跨平台技術擴展。

特別是,uwpwinui 3uno platform之間100%相同地使用xaml相關dll,因此這些平台之間幾乎沒有差異。因此,對wpf開發者來說,使用uno platform桌面版非常有效且具有戰略意義。這是因為從wpf轉換到uno可以在幾小時內完成,轉換到winui 3也非常容易。

未來,wpf技術和基於xaml的框架將繼續發展,利用這些進行跨平台開發將變得更加重要。開發人員需要很好地把握這些趨勢,制定適當的策略,開發高質量的應用程式。

參考

主要倉庫

目前已更新的wpf教程(自定義控制項)

Keep Exploring

延伸阅读

更多文章
同分类 / 同标签 2025/5/27

wpf完成一個危險提醒效果

當我們寫的程式發放出去後,用戶是在進行一些危險操作,我們的軟體應該給一些提醒效果,比如邊框邊緣有紅色,類似與高德地圖那樣子的報警提醒效果

继续阅读
同分类 / 同标签 2024/6/20

codewf.eventbus:輕量級事件總線,讓通信更流暢

codewf.eventbus,一款靈活的事件總線庫,實現模塊間解耦通信。支持多種.net項目類型,如wpf、winforms、asp.net core等。採用簡潔設計,輕鬆實現命令的發布與訂閱、請求與響應。通過有序的事件處理,確保事件得到妥善處理。簡化您的代碼,提升系統可維護性。

继续阅读