首页 > 编程语言 >WPF/C#:实现导航功能

WPF/C#:实现导航功能

时间:2024-07-18 15:32:19浏览次数:7  
标签:C# serviceProvider ViewModel 窗体 wpfui WPF 导航 public 页面

前言

在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_

标签:C#,serviceProvider,ViewModel,窗体,wpfui,WPF,导航,public,页面
From: https://www.cnblogs.com/mingupupu/p/18309608

相关文章

  • pytorch 自定义 dataloader 维度不对齐+广播机制导致不易察觉 bug
    很简单,自定义了一个dataloader,出现以下不易察觉buginputs维度:[bs,4],这个没问题labels维度:正确应该是[bs,1],但是dataloader出来是[bs]模型的outputs维度:[bs,1]如果用torch.mean(torch.abs(labels-outputs))计算L1Loss/MAE由于pytorch的广播机制,torch.ab......
  • 小白C语言基础详解: 运算符
    运算符C语言的运算符非常多,一共有50多种,可以分成若干类。算术运算符算术运算符专门用于算术运算,主要有下面几种。+:正值运算符(一元运算符)-:负值运算符(一元运算符)+:加法运算符(二元运算符)-:减法运算符(二元运算符)*:乘法运算符/:除法运算符%:余值运算符(1)+,-+和-既可以作为一元......
  • HTML5+CSS3小实例:纯CSS实现奥运五环
    实例:纯CSS实现奥运五环技术栈:HTML+CSS效果:源码:【HTML】<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"&g......
  • WLAN与以太网的mac地址修改
    修改WLAN的mac地址打开cmd输入ipconfig/all命令查看WLAN的mac地址和名称将WLAN的名称复制下来打开注册表找到--计算机\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001按下ctrl+F,查找WLAN的名称鼠标右键打开新建,点击字符串值修改mac地址以太网mac地址修改打开设备......
  • 代码随想录算法训练营第16天|LeetCode112路径总和LeetCode113路径总和iiLeetCode106.
    代码随想录算法训练营Day16代码随想录算法训练营第16天|LeetCode112路径总和LeetCode113路径总和iiLeetCode106.从中序与后序遍历序列构造二叉树LeetCode105.从前序与中序遍历序列构造二叉树目录代码随想录算法训练营前言LeetCode112路径总和,LeetCode113路径......
  • 代码随想录算法训练营第 15 天 |LeetCode110平衡二叉树 LeetCode257二叉树的所有路径
    代码随想录算法训练营Day15代码随想录算法训练营第15天|LeetCode110平衡二叉树LeetCode257二叉树的所有路径LeetCode404左叶子之和LeetCode222完全二叉树节点之和目录代码随想录算法训练营前言LeetCode110平衡二叉树LeetCode257二叉树的所有路径LeetCode404左......
  • BTC比特币实时行情API接口
    BTC比特币实时行情API接口#RestfulAPIhttps://tsanghi.com/api/fin/crypto/realtime?token={token}&ticker={ticker}指定加密货币代码,获取该加密货币的实时行情(开、高、低、收)。更新周期:实时。请求方式:GET。#测试比特币接口https://tsanghi.com/api/fin/cr......
  • [misc]-压缩包-zip
    题目:拼在一起解下base64就有flag打开附件,发现都是68个小的压缩文件,且是加密的,根据题目提示,考虑crc32碰撞。importzipfileimportstringimportbinasciiimportbase64d=open('rs.zip','wb')b64=''dic=string.ascii_letters+string.digits+'{}_+/='......
  • C++中关于异常的知识点
    C++中关于异常的知识点异常基本概念异常处理的基本思想C++异常处理的实现异常基本语法栈解旋(unwinding)异常接口声明异常变量生命周期异常的多态使用C++标准异常库标准库介绍编写自己的异常类异常基本概念异常是一种程序控制机制,与函数机制独立和互补函数是一......
  • [米联客-安路飞龙DR1-FPSOC] FPGA基础篇连载-19读写I2C接口EEPROM实验
    软件版本:Anlogic-TD5.9.1-DR1_ES1.1操作系统:WIN1064bit硬件平台:适用安路(Anlogic)FPGA实验平台:米联客-MLK-L1-CZ06-DR1M90G开发板板卡获取平台:https://milianke.tmall.com/登录"米联客"FPGA社区http://www.uisrc.com视频课程、答疑解惑! 1概述......