当前位置: 首页 > news >正文

WPF/C#:实现导航功能

前言

在WPF中使用导航功能可以使用Frame控件,这是比较基础的一种方法。前几天分享了wpfui中NavigationView的基本用法,但是如果真正在项目中使用起来,基础的用法是无法满足的。今天通过wpfui中的mvvm例子来说明在wpfui中如何通过依赖注入与MVVM模式使用导航功能。实践起来,我个人觉得这个例子中实现导航功能还是有点麻烦的,但我也不知道怎么能更优雅,也是学到了一些东西吧。

wpfui中MVVM例子的地址在:https://github.com/lepoco/wpfui/tree/main/src/Wpf.Ui.Demo.Mvvm

实现效果如下所示:

如果你对此感兴趣,可以继续阅读。

实践

使用依赖注入

将主窗体与主窗体的ViewModel与每个页面与每个页面的ViewModel都存入依赖注入容器中:

image-20240718141334286

当然不只是窗体页面与ViewModel,也需要注册一些服务。

为了实现导航功能,使用了两个服务分别是NavigationService与PageService。

NavigationService在wpfui库中已经自带了,直接使用即可:

image-20240718141645305

具体代码可自行研究,这里就不放了。

而PageService在wpfui中没有自带,需要自己定义,MVVM例子中的定义如下所示:

 public class PageService : IPageService{/// <summary>/// Service which provides the instances of pages./// </summary>private readonly IServiceProvider _serviceProvider;/// <summary>/// Initializes a new instance of the <see cref="PageService"/> class and attaches the <see cref="IServiceProvider"/>./// </summary>public PageService(IServiceProvider serviceProvider){_serviceProvider = serviceProvider;}/// <inheritdoc />public T? GetPage<T>()where T : class{if (!typeof(FrameworkElement).IsAssignableFrom(typeof(T))){throw new InvalidOperationException("The page should be a WPF control.");}return (T?)_serviceProvider.GetService(typeof(T));}/// <inheritdoc />public FrameworkElement? GetPage(Type pageType){if (!typeof(FrameworkElement).IsAssignableFrom(pageType)){throw new InvalidOperationException("The page should be a WPF control.");}return _serviceProvider.GetService(pageType) as FrameworkElement;}}

现在已经将所有窗体、页面、ViewModels与相关服务都注册到容器中了。

ViewModel

在MainWindowViewModel中将页面存入一个属性中:

image-20240718142334814

在非首页的ViewModel中实现INavigationAware接口:

image-20240718142456377

View

MainWindow.cs如下所示:

 public partial class MainWindow : INavigationWindow{public ViewModels.MainWindowViewModel ViewModel { get; }public MainWindow(ViewModels.MainWindowViewModel viewModel,IPageService pageService,INavigationService navigationService){ViewModel = viewModel;DataContext = this;Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this);InitializeComponent();SetPageService(pageService);navigationService.SetNavigationControl(RootNavigation);}public INavigationView GetNavigation() => RootNavigation;public bool Navigate(Type pageType) => RootNavigation.Navigate(pageType);public void SetPageService(IPageService pageService) => RootNavigation.SetPageService(pageService);public void ShowWindow() => Show();public void CloseWindow() => Close();/// <summary>/// Raises the closed event./// </summary>protected override void OnClosed(EventArgs e){base.OnClosed(e);// Make sure that closing this window will begin the process of closing the application.Application.Current.Shutdown();}INavigationView INavigationWindow.GetNavigation(){throw new NotImplementedException();}public void SetServiceProvider(IServiceProvider serviceProvider){throw new NotImplementedException();}}

首先实现了INavigationWindow接口。在构造函数中注入所需的依赖类。注意这里的RootNavigation其实就是页面中NavigationView的名称:

image-20240718142925133

刚开始看这里没注意到,卡壳了很久。

因为你在代码中查看定义,它会转到这个地方:

image-20240718143106472

没经验不知道是什么,但是这次过后,知道这是在Xaml中定义,由工具自动生成的代码了。

其他的页面改成了这样的写法:

 public partial class DashboardPage : INavigableView<DashboardViewModel>{public DashboardViewModel ViewModel { get; }public DashboardPage(DashboardViewModel  viewModel){ViewModel = viewModel;this.DataContext = this;InitializeComponent();          }}

都实现了INavigableView<out T>接口:

image-20240718143558501

显示主窗体与主页面

现在准备工作都做好了,下一步就是显示主窗体与主页面了。

在容器中我们也注入了这个:

image-20240718144029024

ApplicationHostService如下所示:

    /// <summary>/// Managed host of the application./// </summary>public class ApplicationHostService : IHostedService{private readonly IServiceProvider _serviceProvider;private INavigationWindow? _navigationWindow;public ApplicationHostService(IServiceProvider serviceProvider){_serviceProvider = serviceProvider;}/// <summary>/// Triggered when the application host is ready to start the service./// </summary>/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>public async Task StartAsync(CancellationToken cancellationToken){await HandleActivationAsync();}/// <summary>/// Triggered when the application host is performing a graceful shutdown./// </summary>/// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>public async Task StopAsync(CancellationToken cancellationToken){await Task.CompletedTask;}/// <summary>/// Creates main window during activation./// </summary>private async Task HandleActivationAsync(){await Task.CompletedTask;if (!System.Windows.Application.Current.Windows.OfType<MainWindow>().Any()){_navigationWindow = (_serviceProvider.GetService(typeof(INavigationWindow)) as INavigationWindow)!;_navigationWindow!.ShowWindow();_ = _navigationWindow.Navigate(typeof(DashboardPage));}await Task.CompletedTask;}}
}

在app.xaml中定义了程序启动与退出事件的处理程序:

image-20240718144223862

 /// <summary>/// Occurs when the application is loading./// </summary>private async void OnStartup(object sender, StartupEventArgs e){await _host.StartAsync();}/// <summary>/// Occurs when the application is closing./// </summary>private async void OnExit(object sender, ExitEventArgs e){await _host.StopAsync();_host.Dispose();}

整个过程回顾

在OnStartup方法中打个断点,理解这个过程:

image-20240718144509901

点击下一步:

image-20240718144922482

到ApplicationHostService中了,一步一步调试,注意这个地方:

image-20240718145229906

因为主窗体实现了INavigationWindow接口,这里获取了主窗体并将主窗体显示,然后调用主窗体中的Navigate方法,导航到DashPage页面,之后点继续,结果如下所示:

image-20240718145523282

最后

以上就是自己最近学习wpfui中导航功能实现的笔记,在自己的项目中也成功使用,对于可能会经常修改代码增加功能的程序这样做感觉挺好的,但是如果你只是使用WPF做一个简单的小工具,感觉这样做增加了复杂度,不用依赖注入,不用做这么复杂的导航,甚至不使用MVVM模式都可以。

Kolors_00012_

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【go】Excelize处理excel表 带合并单元格、自动换行与固定列宽的文件导出
  • JCR一区级 | Matlab实现PSO-Transformer-LSTM多变量回归预测
  • PWM再理解(1)
  • 【Node.js】初识 Node.js
  • CentOS 8中 更新或下载时报错:为仓库 ‘appstream‘ 下载元数据失败 : Cannot prepare internal mirrorlist
  • 彻底解决idea的编解码问题
  • c# .net core中间件,生命周期
  • 物联网实训室的核心功能有哪些?
  • WebPack5.0 快速入门
  • 什么是diff算法?
  • C++20中的constinit说明符
  • 【数据结构】二叉树———Lesson2
  • 十七、【文本编辑器(三)】图像坐标变换
  • 低代码中间件学习体验分享:业务系统的创新引擎
  • 从 Pandas 到 Polars 十八:数据科学 2025,对未来几年内数据科学领域发展的预测或展望
  • [译]CSS 居中(Center)方法大合集
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • 2017前端实习生面试总结
  • Apache Spark Streaming 使用实例
  • canvas 高仿 Apple Watch 表盘
  • gcc介绍及安装
  • Java Agent 学习笔记
  • Java基本数据类型之Number
  • Java精华积累:初学者都应该搞懂的问题
  • Koa2 之文件上传下载
  • Median of Two Sorted Arrays
  • Python爬虫--- 1.3 BS4库的解析器
  • Python语法速览与机器学习开发环境搭建
  • Terraform入门 - 1. 安装Terraform
  • 测试开发系类之接口自动化测试
  • 测试如何在敏捷团队中工作?
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 前端_面试
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 数据科学 第 3 章 11 字符串处理
  • python最赚钱的4个方向,你最心动的是哪个?
  • 阿里云服务器如何修改远程端口?
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • ​linux启动进程的方式
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • ​七周四次课(5月9日)iptables filter表案例、iptables nat表应用
  • # 利刃出鞘_Tomcat 核心原理解析(八)-- Tomcat 集群
  • #HarmonyOS:软件安装window和mac预览Hello World
  • (10)工业界推荐系统-小红书推荐场景及内部实践【排序模型的特征】
  • (39)STM32——FLASH闪存
  • (floyd+补集) poj 3275
  • (TipsTricks)用客户端模板精简JavaScript代码
  • (ZT) 理解系统底层的概念是多么重要(by趋势科技邹飞)
  • (补充)IDEA项目结构
  • (二)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (黑马点评)二、短信登录功能实现
  • (剑指Offer)面试题41:和为s的连续正数序列