WPF/C#:实现导航功能

简介: 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都存入依赖注入容器中:

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

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

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

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

而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中将页面存入一个属性中:

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

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的名称:

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

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

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

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

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

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

显示主窗体与主页面

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

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

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中定义了程序启动与退出事件的处理程序:

/// <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方法中打个断点,理解这个过程:

点击下一步:

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

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

最后

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

目录
相关文章
|
29天前
|
前端开发 C# Windows
在WPF程序中实现PropertyGrid功能
【11月更文挑战第15天】PropertyGrid 是一个用户界面组件,用于直观地查看和编辑对象属性。在 WPF 中可通过组合 Expander 和 DataGrid 实现基本功能,或使用第三方库 PropertyTools 获得更强大特性,包括属性验证和类型特定编辑器。
|
1月前
|
开发框架 C# iOS开发
基于C#开源、功能强大、灵活的跨平台开发框架 - Uno Platform
基于C#开源、功能强大、灵活的跨平台开发框架 - Uno Platform
|
1月前
|
网络协议 Unix Linux
精选2款C#/.NET开源且功能强大的网络通信框架
精选2款C#/.NET开源且功能强大的网络通信框架
|
2月前
|
监控 前端开发 安全
C#一分钟浅谈:文件上传与下载功能实现
【10月更文挑战第2天】在Web应用开发中,文件的上传与下载是常见需求。本文从基础入手,详细讲解如何在C#环境下实现文件上传与下载。首先介绍前端表单设计及后端接收保存方法,使用`&lt;input type=&quot;file&quot;&gt;`与`IFormFile`接口;接着探讨错误处理与优化策略,如安全性验证和路径管理;最后讲解文件下载的基本步骤,包括确定文件位置、设置响应头及发送文件流。此外,还提供了进阶技巧,如并发处理、大文件分块上传及进度监控,帮助开发者构建更健壮的应用系统。
149 15
|
2月前
|
开发框架 Cloud Native .NET
10 个 C# 关键字和功能
10 个 C# 关键字和功能
59 8
C# WPF 中 外部图标引入iconfont,无法正常显示问题 【小白记录】
本文介绍了在C# WPF应用程序中引入外部iconfont图标时可能遇到的显示问题及其解决方法:1) 检查资源路径和引入格式是否正确,确保字体文件引用格式为“#xxxx”,并正确指向字体文件位置;2) 确保图标资源被包含在程序集中,通过设置字体文件的生成操作为Resource(资源)来实现。
C# WPF 中 外部图标引入iconfont,无法正常显示问题 【小白记录】
|
3月前
|
编解码 C# 数据库
C# + WPF 音频播放器 界面优雅,体验良好
【9月更文挑战第18天】这是一个用 C# 和 WPF 实现的音频播放器示例,界面简洁美观,功能丰富。设计包括播放/暂停按钮、进度条、音量控制滑块、歌曲列表和专辑封面显示。功能实现涵盖音频播放、进度条控制、音量调节及歌曲列表管理。通过响应式设计、动画效果、快捷键支持和错误处理,提升用户体验。可根据需求扩展更多功能。
139 3
|
2月前
|
物联网 C#
【C#】简单的蓝牙通讯功能实现
【C#】简单的蓝牙通讯功能实现
55 0
|
2月前
|
C#
如何使用c# 实现断点续传功能
如何使用c# 实现断点续传功能
31 0
|
2月前
|
网络协议 C#
C#:简化版的实现断点续传功能
C#:简化版的实现断点续传功能
38 0