手把手搞懂 WPF UI 应用宿主与依赖注入:从服务注册到优雅退出
2026/9/18 19:01:31 网站建设 项目流程

手把手搞懂 WPF UI 应用宿主与依赖注入:从服务注册到优雅退出

【免费下载链接】wpfuiWPF UI provides the Fluent experience in your known and loved WPF framework. Intuitive design, themes, navigation and new immersive controls. All natively and effortlessly.项目地址: https://gitcode.com/GitHub_Trending/wp/wpfui

跑示例项目时主窗口不显示、改主题不生效、退出时进程卡住,这三个症状是 WPF UI 应用生命周期里最容易卡住人的地方。WPF UI 的示例项目用.NET Generic Host管理全部启动流程,覆盖依赖注入(Dependency Injection, DI)注册、页面导航、主题与任务栏进度、退出时停止服务。读完你可以把自定义页面和服务注册进容器,并定位窗口不显示、主题不跟随这类问题。

🧩 机制拆解:WPF UI 的依赖注入容器怎么构建

依赖注入容器怎么构建:所有服务在一个地方注册

WPF 本身没有服务容器,示例借用 .NET Generic Host 把配置、日志和依赖注入放在一处,主窗口和 ViewModel 也就成了容器里"普通得不能再普通"的注册对象。关键注册在 samples/Wpf.Ui.Demo.Mvvm/App.xaml.cs:

// samples/Wpf.Ui.Demo.Mvvm/App.xaml.cs:ConfigureServices 里注册应用的全部服务 .ConfigureServices((context, services) => { _ = services.AddNavigationViewPageProvider(); _ = services.AddHostedService<ApplicationHostService>(); _ = services.AddSingleton<IThemeService, ThemeService>(); _ = services.AddSingleton<ITaskBarService, TaskBarService>(); _ = services.AddSingleton<INavigationService, NavigationService>(); _ = services.AddSingleton<INavigationWindow, Views.MainWindow>(); _ = services.AddSingleton<Views.Pages.SettingsPage>(); _ = services.Configure<AppConfig>(context.Configuration.GetSection(nameof(AppConfig))); })

你要加新服务时,改这里加一行AddSingleton就够了,窗口或 ViewModel 通过构造函数注入拿到它,别处不用动。

主窗口为什么由托管服务创建,而不是写在 App 里

把建窗口的逻辑直接塞进OnStartup,会把"应用何时启动"和"窗口怎么建"两件事搅在一起。示例把它挪进托管服务(IHostedService),启动变成标准的StartAsync流程,退出时StopAsync也对称地有了位置:

// samples/Wpf.Ui.Demo.Mvvm/Services/ApplicationHostService.cs:宿主就绪后从容器取窗口并显示 private async Task HandleActivationAsync() { if (!Application.Current.Windows.OfType<MainWindow>().Any()) { _navigationWindow = (serviceProvider.GetService(typeof(INavigationWindow)) as INavigationWindow)!; _navigationWindow!.ShowWindow(); _ = _navigationWindow.Navigate(typeof(Views.Pages.DashboardPage)); } }

要换初始页面,就改Navigate的类型参数;要加启动前置逻辑(比如登录检查),写在HandleActivationAsync调用之前。

导航服务和导航窗口,为什么拆成两个接口

INavigationService只管导航控件,不依赖窗口,ViewModel 想调导航就不必持有窗口引用;INavigationWindow管窗口本身,负责显示、关闭和导航。两者靠窗口构造函数里的navigationService.SetNavigationControl(RootNavigation)这一行接上,见 samples/Wpf.Ui.Demo.Mvvm/Views/MainWindow.xaml.cs。你要从别处打开新页面时,对照 src/Wpf.Ui/INavigationService.cs 的接口定义,Navigate(Type)系列重载就是你的入口。

🚀 场景实战:导航、主题切换、任务栏进度条怎么接

导航到自己的页面:页面怎么注册进导航

新加一个设置页,你希望它能被代码导航到。关键是"先注册,再导航",导航服务靠 DI 容器解析页面实例:

// 在 samples/Wpf.Ui.Demo.Mvvm/App.xaml.cs 注册页面,再在能拿到 INavigationService 处导航 services.AddSingleton<Views.Pages.SettingsPage>(); _navigationService.Navigate(typeof(Views.Pages.SettingsPage));

跑起来后:调用Navigate会立即把内容区切到你的新页面;把入口同时加进导航栏的数据源里,点菜单项效果一致。

主题和强调色怎么切:一行进深色

导航跑通后,被问得最多的问题是外观怎么换。用户要在设置页切浅色/深色,还要强调色跟着系统走,IThemeService一次覆盖:

// ThemeService 已在 samples/Wpf.Ui.Demo.Mvvm/App.xaml.cs 注册,构造函数注入 IThemeService 后调用 themeService.SetTheme(ApplicationTheme.Dark); themeService.SetSystemAccent(); _ = themeService.GetTheme();

跑起来后:窗口背景和控件配色整体立即切换,SetSystemAccent让应用强调色改用当前 Windows 个性化颜色。想让窗口自动跟随 Windows 的明暗模式,就在主窗口构造函数里加一行Appearance.SystemThemeWatcher.Watch(this);,示例的 Views/MainWindow.xaml.cs 就是这么做的。

任务栏进度条怎么接:长操作有进度可看

导出文件这类长操作,用户不想专门切回来窗口看进度,把进度条挂到任务栏图标上即可:

// TaskBarService 已在 samples/Wpf.Ui.Demo.Mvvm/App.xaml.cs 注册,构造函数注入 ITaskBarService 后调用 taskBarService.SetValue(mainWindow, TaskBarProgressState.Normal, 50, 100); taskBarService.SetState(mainWindow, TaskBarProgressState.None);

跑起来后:任务栏里的应用图标下方出现进度条,第一行把它刷到 50%,操作结束用第二行把进度条清掉。

🛠️ 排错手册:WPF UI 应用生命周期的高频问题

症状:应用启动后没有任何窗口。原因:窗口只在托管服务的StartAsync里创建,ApplicationHostService没注册、或窗口没注册成INavigationWindow时,容器取到空引用,建窗被跳过。 解法:在 App.xaml.cs 的ConfigureServices里补上services.AddHostedService<ApplicationHostService>();services.AddSingleton<INavigationWindow, Views.MainWindow>();,并确认你的窗口类实现了INavigationWindow且写好了ShowWindow()

症状:导航后白屏或抛异常,页面出不来。原因:导航服务靠INavigationViewPageProvider从容器解析页面,没注册的页面类型解析不到。 解法:ConfigureServices里加services.AddSingleton<你的页面类型>();,并确认services.AddNavigationViewPageProvider();那一行存在;换成自己的窗口后仍不生效,检查窗口构造函数是否执行了navigationService.SetNavigationControl(RootNavigation)(见 Views/MainWindow.xaml.cs 第 27 行)。

症状:主题不跟随 Windows 明暗模式。原因:跟随系统靠SystemThemeWatcher订阅窗口,没调用时窗口永远停在初始主题。 解法:主窗口构造函数里加Appearance.SystemThemeWatcher.Watch(this);,示例的 Views/MainWindow.xaml.cs 第 23 行就有这一行。

症状:窗口点叉关闭了,进程却不结束。原因:WPF 只在所有窗口关闭后才结束进程,示例用OnClosed主动触发收尾,App.OnExit再停托管服务。 解法:你的主窗口覆写OnClosed并调用Application.Current.Shutdown();同时确认 App.xaml.cs 的OnExit里有await _host.StopAsync();_host.Dispose();两行(第 90-95 行)。

症状:SetTheme返回false,以为没生效。原因:这不是 bug,ThemeService.SetTheme会先比较当前主题,没变化时直接返回false,见 src/Wpf.Ui/ThemeService.cs 第 45-48 行。 解法:设置前先用GetTheme()看当前值,或处理掉false返回值。

📚 扩展指引:跑通示例后还能往哪看

  • samples/Wpf.Ui.Demo.Mvvm/ — 完整宿主加 DI 示例,适合想看全流程的人
  • src/Wpf.Ui/ — 核心库源码,适合想抠服务实现细节的人
  • src/Wpf.Ui.DependencyInjection/ — 页面提供器 DI 扩展,适合想换页面来源的人
  • docs/documentation/getting-started.md — 入门文档,适合第一次搭项目的人
  • docs/migration/v4-migration.md — V4 迁移指南,适合升级旧版本的人

行动清单

  • 克隆仓库跑一遍示例:git clone https://gitcode.com/GitHub_Trending/wp/wpfui,然后打开运行samples/Wpf.Ui.Demo.Mvvm
  • 在你自己的项目里照场景一的写法注册一个页面,调用Navigate验证页面能切出来。
  • OnExit里加日志观察托管服务停止顺序,确认退出流程干净。

收个尾:机制拆解回答了服务容器为什么这样搭,场景实战给你三组能直接照抄的操作,排错手册收了你最可能撞上的五个症状,扩展指引标了下一步该看哪些文件。下一篇讲 WPF UI 的导航控件NavigationView,把菜单项、页签和返回栈的内部实现拆开看。

【免费下载链接】wpfuiWPF UI provides the Fluent experience in your known and loved WPF framework. Intuitive design, themes, navigation and new immersive controls. All natively and effortlessly.项目地址: https://gitcode.com/GitHub_Trending/wp/wpfui

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询