使用 Hosting 构建 WPF 程序 - prism 篇

news/2025/3/22 11:26:37/文章来源:https://www.cnblogs.com/jasongrass/p/18746206

在 使用 Hosting 构建 WPF 程序 - Stylet 篇 中,使用 Hosting + Stylet 的方式,构建了一个 WPF 框架,
本文用于记录使用 .NET Generic Host + Prism 构建 WPF 所需的修改,仅供参考。

示例代码:Jasongrass/Demo.AppHostPrism: WPF + Prism + Hosting

🍕 1 初始化构建

新建一个 WPF 项目,修改 .csproj 和 App.cs

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>WinExe</OutputType><TargetFramework>net8.0-windows</TargetFramework><Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings><UseWPF>true</UseWPF></PropertyGroup><ItemGroup><PackageReference Include="Prism.Core" Version="9.0.537" /><PackageReference Include="Prism.Wpf" Version="9.0.537" /><PackageReference Include="Prism.DryIoc" Version="9.0.537" /><PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" /><PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" /><PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0-preview3" /></ItemGroup><ItemGroup><ApplicationDefinition Remove="App.xaml" /><Page Include="App.xaml" /></ItemGroup><ItemGroup><Compile Update="App.startup.cs"><DependentUpon>App.xaml</DependentUpon></Compile><Compile Update="App.static.cs"><DependentUpon>App.xaml</DependentUpon></Compile></ItemGroup></Project>

App.xamlApplicationDefinition 修改为 Page,移除自启动特性,待会添加手动的 Main 函数启动。

下面这里的修改,只是增加了两个文件,App.static.csApp.startup.cs,让他们看起来属于 App.xaml,具体内容见后面。

<ItemGroup><Compile Update="App.startup.cs"><DependentUpon>App.xaml</DependentUpon></Compile><Compile Update="App.static.cs"><DependentUpon>App.xaml</DependentUpon></Compile>
</ItemGroup>

同时,App.xaml 内的 App,需要修改成 PrismApplication

<prism:PrismApplicationx:Class="WpfAppPrismT1.App"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:WpfAppPrismT1"xmlns:prism="http://prismlibrary.com/"><Application.Resources></Application.Resources>
</prism:PrismApplication>

🍕 2 Main 方法与服务注册

以下是 App.static.cs 的内容。

public partial class App
{private static IServiceCollection? _serviceCollection;private static IServiceProvider? _serviceProvider;private static IContainer? _appContainer;public static IServiceProvider ServiceProvider{get => _serviceProvider!;private set => _serviceProvider = value;}public static IContainer AppContainer{get{if (_appContainer == null){throw new InvalidOperationException("App Container not init");}return _appContainer;}private set{if (_appContainer != null){throw new InvalidOperationException("App Container had been initialized");}_appContainer = value;}}[STAThread]static void Main(string[] args){using IHost host = CreateHostBuilder(args).Build();ServiceProvider = host.Services;host.StartAsync();var app = new App();app.InitializeComponent();app.Run();}private static IHostBuilder CreateHostBuilder(string[] args){var builder = Host.CreateDefaultBuilder(args).ConfigureServices(serviceCollection =>{// 在这里注册的服务,通过 ServiceProvider 和 AppContainer 都可以拿到// 因为 prism 的相关服务,是在 App 启动之后才注册,所以通过 ServiceProvider 就拿不到,需要通过 DryIoc 的 AppContainer 去拿。_serviceCollection = serviceCollection;ConfigureServicesBeforeAppLaunch(serviceCollection);});return builder;}public static T GetService<T>()where T : class{var service = AppContainer.GetService<T>();if (service == null){throw new InvalidOperationException($"Cannot get service if {typeof(T).Name}");}return service;}
}

以下是 App.xaml.cs 的内容。

public partial class App : PrismApplication
{protected override void RegisterTypes(IContainerRegistry containerRegistry){containerRegistry.RegisterForNavigation<Page1View>();containerRegistry.RegisterForNavigation<Page2View>();}protected override IContainerExtension CreateContainerExtension(){if (_serviceCollection == null){throw new InvalidOperationException("Application Startup Error. Cannot found microsoft dependency injection service collection");}ConfigureServicesWhenAppLaunch(_serviceCollection);var container = new DryIoc.Container(CreateContainerRules());var newContainer = container.WithDependencyInjectionAdapter(_serviceCollection);AppContainer = newContainer;return new DryIocContainerExtension(newContainer);}protected override Window CreateShell(){return Container.Resolve<MainWindow>();}
}

以下是 App.startup.cs 的内容。

public partial class App
{/// <summary>/// 在这里注册的服务,通过 ServiceProvider 和 AppContainer 都可以拿到/// </summary>/// <param name="services"></param>private static void ConfigureServicesBeforeAppLaunch(IServiceCollection services){services.AddSingleton(_ => Current.Dispatcher);services.AddSingleton<IMyService, MyService>();}/// <summary>/// 在这里注册的服务,只能通过 AppContainer 拿到/// </summary>/// <param name="services"></param>private void ConfigureServicesWhenAppLaunch(IServiceCollection services){// 在这里注册的服务,只能通过 AppContainer 拿到// services.AddSingleton<IMyService, MyService>();}
}

2.1 使用 Microsoft.DependencyInjection 来实现 DryIoc

一些有 IOC 支持的类库的扩展方法,可能只支持 Microsoft.DependencyInjection,并且 Microsoft Hosting 中的配置,日志等依赖,也都放在 Microsoft.DependencyInjection 中,
所以这里想要使用 Microsoft.DependencyInjection 来代替默认的 DryIoc 的实现。

DryIoc.Microsoft.DependencyInjection 这个包提供了这个支持,关键代码在 CreateContainerExtension 方法中。

使用 Prism 时,同步引入 Prism.DryIoc 这个包,Prism 相关的服务,就统一被 DryIoc 管理了。

这里需要注意的是,代码中有 Microsoft.DependencyInjection 的 ServiceProvider 和 DryIoc 的 AppContainer,实际使用时,应该使用 AppContainer
这样才能把 Prism 以及 App 启动时注册的服务纳入其中。
在实际开发时,不应该暴露 ServiceProvider 给外部使用,容易造成误操作。

具体原因还没有深究,或许是这里的 Microsoft.DependencyInjection 替换实现有点问题?

2.2 App.startup.cs

App.startup.cs 中放了 ConfigureServicesBeforeAppLaunchConfigureServicesWhenAppLaunch 两个注册服务的入口。
在实际使用中,有其实一个就可以。这里只是为了演示两个不同的服务注册时机。

🍕 3 CommunityToolkit.Mvvm

虽然使用了 Prism,但在基础的 ViewModel 的属性通知,属性校验,RelayCommand 的书写体验和功能上,感觉还是 CommunityToolkit.Mvvm 更胜一筹。
Prism 的 ViewModel 的基类 BindableBase 其实功能很简单,不如 CommunityToolkit.Mvvm 丰富。

而且,prism 对于导航操作,View 与 ViewModel 的绑定等,并不会强制要求 ViewModel 必须继承 BindableBase 这个基类,很多扩展功能,比如 INavigationAware 等,prism 的设计也是基于接口的,而不是基于 BindableBase 内部的实现。这点就非常棒,为自定义 ViewModel 的实现留足了空间。

CommunityToolkit.MvvmPrism 可以非常好地共存。

🍕 4 参考资料

Prism框架与Microsoft.Extensions.DependencyInjection的集成使用笔记 - 非法关键字 - 博客园

github - PrismLibrary/Prism

Introduction to Prism | Prism

.NET 6.0 + WPF 使用 Prism 框架实现导航 - 小码编匠 - 博客园

Prism程序入口、View ViewModel关联、数据绑定、数据校验、cmd - AJun816 - 博客园

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/903415.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何构建一个用于3D扫描的电动转台

在这个项目中,我将向您展示如何构建一个用于3D扫描的电动转台。这个DIY项目由三个主要部分组成:顶板、中央齿轮驱动板和底座。顶板保持静止,作为放置待扫描物体的平台。在设计的核心,中间板的特点是一个内部齿轮机构,隐藏在视线之外,这是由一个步进电机驱动。这个齿轮板旋…

【每日一题】20250322

【每日一题】 1.(18分) \(\hspace{0.7cm}\)I.(5分)根据单摆周期公式 \(\displaystyle T=2\pi\sqrt{\frac{l}{g}}\),可以通过实验测量当地的重力加速度.如图 \(1\) 所示,将细线的上端固定在铁架台上,下端系一小钢球,就做成了单摆.\(\hspace{0.7cm}\)(1)用游标卡尺测…

Git 和 GitHub 笔记

笔记说明 该笔记记录所有我使用 Git 和 GitHub 过程中遇到的、觉得需要记录的事情。复杂的问题按需抽出来单独称为一篇笔记,换成博客链接。 修改远程仓库名字后,本地仓库做什么处理? git remote -v git remote set-url origin https://github.com/用户名/新仓库名.git or gi…

外设驱动库开发笔记55:MAX31865热电阻变送器驱动

外设驱动库开发笔记55:MAX31865热电阻变送器驱动热敏电阻(RTD)作为一种高精度的温度传感器,在工业控制、医疗设备和实验室测量等领域有着广泛的应用。MAX31865是Maxim Integrated推出的一款专为RTD设计的信号调理器,能够简化RTD的温度测量过程。本文将详细介绍如何设计一个…

【20250322】热烈庆祝《青猪》系列第二季定档7月

www七年终于等来了 初中的时候还经常在班上跟同学聊这番好耶!考个好成绩,暑假一边学CSAPP一边看!

异常--java进阶day08

1.异常 java中,所有的异常都是类2.异常的体系结构3.编译时异常与运行时异常 1.编译时异常 语法完全正确,但是代码就是会报错,如下图上图中,写的是时间格式化类的使用,parse方法将给的字符串变为时间类对象 明明语法正确,但是系统就是会报错,这就是编译时异常 原因很简单…

leetcode每日一题:监控二叉树

引言 ​ 今天的每日一题原题是2643. 一最多的行,直接模拟,切除和最大的一行即可。更换成前几天遇到的更有意思的一题来写这个每日一题。 题目 968. 监控二叉树 给定一个二叉树,我们在树的节点上安装摄像头。 节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。 计…

Ubuntu安装GPU驱动+CUDA+cuDNN的安装方法

一台有GPU的虚拟机如果没有安装CUDA的驱动,是需要我们手动去进行安装的,介绍Ubuntu操作系统的安装教程。 1. 下载安装文件 NVIDIA CUDA Toolkit Archive 点击上面链接,进入CUDA Toolkit Archive,下载需要的CUDA工具包,本文章以CUDA Toolkit 12.3.2版本举例(可能需要代理加…

[多项式学习笔记] 拉格朗日插值

[多项式学习笔记] 拉格朗日插值[多项式学习笔记] 拉格朗日插值 多项式插值 给定 \(x\) 坐标两两不同的 \(n + 1\) 个点,能够唯一确定一个 \(n\) 次多项式。从给定点求出多项式的过程称为插值。 具体而言,给定 \(n + 1\) 个点 \((x_0, y_0), (x_1, y_1), \cdots, (x_n, y_n)\)…

flutter:bottomNavigationBar+PageView切换页面,使页面可以滑动切换

一,代码: tabbar: import package:flutter/material.dart; import ../tabpages/MyHomePage.dart; import ../tabpages/ProfilePage.dart;class MyTabBar extends StatefulWidget {const MyTabBar({super.key});@overrideState<MyTabBar> createState() => _MyTabBar…

flutter:用底部导航栏切换页面

一,代码: tabbar页面: import package:flutter/material.dart; import ../tabpages/MyHomePage.dart; import ../tabpages/ProfilePage.dart;class MyTabBar extends StatefulWidget {const MyTabBar({super.key});@overrideState<MyTabBar> createState() => _MyTa…

ASE13N45-ASEMI照明驱动专用ASE13N45

ASE13N45-ASEMI照明驱动专用ASE13N45编辑:LL ASE13N45-ASEMI照明驱动专用ASE13N45 型号:ASE13N45 品牌:ASEMI 封装:TO-220F 最大漏源电流:13A 漏源击穿电压:450V 批号:最新 RDS(ON)Max:0.45Ω 引脚数量:3 沟道类型:N沟道MOS管 封装尺寸:如图 特性:MOS管、N沟道M…