百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

NET Core 3 WPF MVVM框架 Prism系列之导航系统

ccwgpt 2025-04-06 14:19 25 浏览 0 评论

本文将介绍如何在.NET Core3环境下使用MVVM框架Prism基于区域Region的导航系统

在讲解Prism导航系统之前,我们先来看看一个例子,我在之前的demo项目创建一个登录界面:

我们看到这里是不是一开始想象到使用WPF带有的导航系统,通过Frame和Page进行页面跳转,然后通过导航日志的GoBack和GoForward实现后退和前进,其实这是通过使用Prism的导航框架实现的,下面我们来看看如何在Prism的MVVM模式下实现该功能

一.区域导航#

我们在上一篇介绍了Prism的区域管理,而Prism的导航系统也是基于区域的,首先我们来看看如何在区域导航

1.注册区域#

LoginWindow.xaml:

Copy
    
        
            
        
    
    
        
    

2.注册导航#

App.cs:

Copy  protected override void RegisterTypes(IContainerRegistry containerRegistry)
  {
        containerRegistry.Register();
        containerRegistry.Register();
        containerRegistry.Register();

        //注册全局命令
        containerRegistry.RegisterSingleton();
        containerRegistry.RegisterInstance(Container.Resolve());

        //注册导航
        containerRegistry.RegisterForNavigation();
        containerRegistry.RegisterForNavigation();
  }

3.区域导航#

LoginWindowViewModel.cs:

Copypublic class LoginWindowViewModel:BindableBase
{

    private readonly IRegionManager _regionManager;
    private readonly IUserService _userService;
    private DelegateCommand _loginLoadingCommand;
    public DelegateCommand LoginLoadingCommand =>
            _loginLoadingCommand ?? (_loginLoadingCommand = new DelegateCommand(ExecuteLoginLoadingCommand));

    void ExecuteLoginLoadingCommand()
    {
        //在LoginContentRegion区域导航到LoginMainContent
        _regionManager.RequestNavigate(RegionNames.LoginContentRegion, "LoginMainContent");

         Global.AllUsers = _userService.GetAllUsers();
    }

    public LoginWindowViewModel(IRegionManager regionManager, IUserService userService)
    {
          _regionManager = regionManager;
          _userService = userService;            
    }

}

LoginMainContentViewModel.cs:

Copypublic class LoginMainContentViewModel : BindableBase
{
    private readonly IRegionManager _regionManager;

    private DelegateCommand _createAccountCommand;
    public DelegateCommand CreateAccountCommand =>
            _createAccountCommand ?? (_createAccountCommand = new DelegateCommand(ExecuteCreateAccountCommand));

    //导航到CreateAccount
    void ExecuteCreateAccountCommand()
    {
         Navigate("CreateAccount");
    }

    private void Navigate(string navigatePath)
    {
         if (navigatePath != null)
              _regionManager.RequestNavigate(RegionNames.LoginContentRegion, navigatePath);
        
    }


    public LoginMainContentViewModel(IRegionManager regionManager)
    {
         _regionManager = regionManager;
    }

 }

效果如下:

这里我们可以看到我们调用RegionMannager的RequestNavigate方法,其实这样看不能很好的说明是基于区域的做法,如果将换成下面的写法可能更好理解一点:

Copy   //在LoginContentRegion区域导航到LoginMainContent
  _regionManager.RequestNavigate(RegionNames.LoginContentRegion, "LoginMainContent");

换成

Copy //在LoginContentRegion区域导航到LoginMainContent
 IRegion region = _regionManager.Regions[RegionNames.LoginContentRegion];
 region.RequestNavigate("LoginMainContent");

其实RegionMannager的RequestNavigate源码也是大概实现也是大概如此,就是去调Region的RequestNavigate的方法,而Region的导航是实现了一个INavigateAsync接口:

Copypublic interface INavigateAsync
{
   void RequestNavigate(Uri target, Action navigationCallback);
   void RequestNavigate(Uri target, Action navigationCallback, NavigationParameters navigationParameters);
    
}   

我们可以看到有RequestNavigate方法三个形参:

  • target:表示将要导航的页面Uri
  • navigationCallback:导航后的回调方法
  • navigationParameters:导航传递参数(下面会详解)

那么我们将上述加上回调方法:

Copy //在LoginContentRegion区域导航到LoginMainContent
 IRegion region = _regionManager.Regions[RegionNames.LoginContentRegion];
 region.RequestNavigate("LoginMainContent", NavigationCompelted);

 private void NavigationCompelted(NavigationResult result)
 {
     if (result.Result==true)
     {
         MessageBox.Show("导航到LoginMainContent页面成功");
     }
     else
     {
         MessageBox.Show("导航到LoginMainContent页面失败");
     }
 }

效果如下:

二.View和ViewModel参与导航过程#

1.INavigationAware#

我们经常在两个页面之间导航需要处理一些逻辑,例如,LoginMainContent页面导航到CreateAccount页面时候,LoginMainContent退出页面的时刻要保存页面数据,导航到CreateAccount页面的时刻处理逻辑(例如获取从LoginMainContent页面的信息),Prism的导航系统通过一个INavigationAware接口:

Copy    public interface INavigationAware : Object
    {
        Void OnNavigatedTo(NavigationContext navigationContext);

        Boolean IsNavigationTarget(NavigationContext navigationContext);

        Void OnNavigatedFrom(NavigationContext navigationContext);
    }
  • OnNavigatedFrom:导航之前触发,一般用于保存该页面的数据
  • OnNavigatedTo:导航后目的页面触发,一般用于初始化或者接受上页面的传递参数
  • IsNavigationTarget:True则重用该View实例,Flase则每一次导航到该页面都会实例化一次

我们用代码来演示这三个方法:

LoginMainContentViewModel.cs:

Copypublic class LoginMainContentViewModel : BindableBase, INavigationAware
{
     private readonly IRegionManager _regionManager;

     private DelegateCommand _createAccountCommand;
     public DelegateCommand CreateAccountCommand =>
            _createAccountCommand ?? (_createAccountCommand = new DelegateCommand(ExecuteCreateAccountCommand));

     void ExecuteCreateAccountCommand()
     {
         Navigate("CreateAccount");
     }

     private void Navigate(string navigatePath)
     {
         if (navigatePath != null)
            _regionManager.RequestNavigate(RegionNames.LoginContentRegion, navigatePath);
     }

     public LoginMainContentViewModel(IRegionManager regionManager)
     {
          _regionManager = regionManager;
     }

     public bool IsNavigationTarget(NavigationContext navigationContext)
     {            
          return true;
     }

     public void OnNavigatedFrom(NavigationContext navigationContext)
     {
          MessageBox.Show("退出了LoginMainContent");
     }

     public void OnNavigatedTo(NavigationContext navigationContext)
     {
          MessageBox.Show("从CreateAccount导航到LoginMainContent");
     }
 }

CreateAccountViewModel.cs:

Copypublic class CreateAccountViewModel : BindableBase,INavigationAware
{
     private DelegateCommand _loginMainContentCommand;
     public DelegateCommand LoginMainContentCommand =>
            _loginMainContentCommand ?? (_loginMainContentCommand = new DelegateCommand(ExecuteLoginMainContentCommand));

     void ExecuteLoginMainContentCommand()
     {
         Navigate("LoginMainContent");
     }

     public CreateAccountViewModel(IRegionManager regionManager)
     {
         _regionManager = regionManager;
     }

     private void Navigate(string navigatePath)
     {
        if (navigatePath != null)
            _regionManager.RequestNavigate(RegionNames.LoginContentRegion, navigatePath);
     }

     public bool IsNavigationTarget(NavigationContext navigationContext)
     {
         return true;
     }

     public void OnNavigatedFrom(NavigationContext navigationContext)
     {
         MessageBox.Show("退出了CreateAccount");
     }

     public void OnNavigatedTo(NavigationContext navigationContext)
     {
         MessageBox.Show("从LoginMainContent导航到CreateAccount");
     }

 }

效果如下:

修改IsNavigationTarget为false:

Copypublic class LoginMainContentViewModel : BindableBase, INavigationAware
{
     public bool IsNavigationTarget(NavigationContext navigationContext)
     {            
          return false;
     }
}

public class CreateAccountViewModel : BindableBase,INavigationAware
{
     public bool IsNavigationTarget(NavigationContext navigationContext)
     {
         return false;
     }
 }

效果如下:

我们会发现LoginMainContent和CreateAccount页面的数据不见了,这是因为第二次导航到页面的时候当IsNavigationTarget为false时,View将会重新实例化,导致ViewModel也重新加载,因此所有数据都清空了

2.IRegionMemberLifetime#

同时,Prism还可以通过IRegionMemberLifetime接口的KeepAlive布尔属性控制区域的视图的生命周期,我们在上一篇关于区域管理器说到,当视图添加到区域时候,像ContentControl这种单独显示一个活动视图,可以通过RegionActivateDeactivate方法激活和失效视图,像ItemsControl这种可以同时显示多个活动视图的,可以通过RegionAddRemove方法控制增加活动视图和失效视图,而当视图的KeepAlivefalseRegionActivate另外一个视图时,则该视图的实例则会去除出区域,为什么我们不在区域管理器讲解该接口呢?因为当导航的时候,同样的是在触发了RegionActivateDeactivate,当有IRegionMemberLifetime接口时则会触发RegionAddRemove方法,这里可以去看下Prism的
RegionMemberLifetimeBehavior源码

我们将LoginMainContentViewModel实现IRegionMemberLifetime接口,并且把KeepAlive设置为false,同样的将IsNavigationTarget设置为true

LoginMainContentViewModel.cs:

Copypublic class LoginMainContentViewModel : BindableBase, INavigationAware,IRegionMemberLifetime
{

     public bool KeepAlive => false;
     
     private readonly IRegionManager _regionManager;

     private DelegateCommand _createAccountCommand;
     public DelegateCommand CreateAccountCommand =>
            _createAccountCommand ?? (_createAccountCommand = new DelegateCommand(ExecuteCreateAccountCommand));

     void ExecuteCreateAccountCommand()
     {
         Navigate("CreateAccount");
     }

     private void Navigate(string navigatePath)
     {
         if (navigatePath != null)
            _regionManager.RequestNavigate(RegionNames.LoginContentRegion, navigatePath);
     }

     public LoginMainContentViewModel(IRegionManager regionManager)
     {
          _regionManager = regionManager;
     }

     public bool IsNavigationTarget(NavigationContext navigationContext)
     {            
          return true;
     }

     public void OnNavigatedFrom(NavigationContext navigationContext)
     {
          MessageBox.Show("退出了LoginMainContent");
     }

     public void OnNavigatedTo(NavigationContext navigationContext)
     {
          MessageBox.Show("从CreateAccount导航到LoginMainContent");
     }
 }

效果如下:

我们会发现跟没实现IRegionMemberLifetime接口和IsNavigationTarget设置为false情况一样,当KeepAlivefalse时,通过断点知道,重新导航回LoginMainContent页面时不会触发IsNavigationTarget方法,因此可以

知道判断顺序是:KeepAlive -->IsNavigationTarget

3.IConfirmNavigationRequest#

Prism的导航系统还支持再导航前允许是否需要导航的交互需求,这里我们在CreateAccount注册完用户后寻问是否需要导航回LoginMainContent页面,代码如下:

CreateAccountViewModel.cs:

Copypublic class CreateAccountViewModel : BindableBase, INavigationAware,IConfirmNavigationRequest
{
     private DelegateCommand _loginMainContentCommand;
     public DelegateCommand LoginMainContentCommand =>
            _loginMainContentCommand ?? (_loginMainContentCommand = new DelegateCommand(ExecuteLoginMainContentCommand));
    
     private DelegateCommand<object> _verityCommand;
     public DelegateCommand<object> VerityCommand =>
            _verityCommand ?? (_verityCommand = new DelegateCommand<object>(ExecuteVerityCommand));

     void ExecuteLoginMainContentCommand()
     {
         Navigate("LoginMainContent");
     }

     public CreateAccountViewModel(IRegionManager regionManager)
     {
         _regionManager = regionManager;
     }

     private void Navigate(string navigatePath)
     {
        if (navigatePath != null)
            _regionManager.RequestNavigate(RegionNames.LoginContentRegion, navigatePath);
     }

     public bool IsNavigationTarget(NavigationContext navigationContext)
     {
         return true;
     }

     public void OnNavigatedFrom(NavigationContext navigationContext)
     {
         MessageBox.Show("退出了CreateAccount");
     }

     public void OnNavigatedTo(NavigationContext navigationContext)
     {
         MessageBox.Show("从LoginMainContent导航到CreateAccount");
     }
    
     //注册账号
     void ExecuteVerityCommand(object parameter)
     {
         if (!VerityRegister(parameter))
         {
                return;
         }
         MessageBox.Show("注册成功!");
         LoginMainContentCommand.Execute();
     }
    
     //导航前询问
     public void ConfirmNavigationRequest(NavigationContext navigationContext, Action continuationCallback)
     {
         var result = false;
         if (MessageBox.Show("是否需要导航到LoginMainContent页面?", "Naviagte?",MessageBoxButton.YesNo) ==MessageBoxResult.Yes)
         {
             result = true;
         }
         continuationCallback(result);
      }
 }

效果如下:

三.导航期间传递参数#

Prism提供NavigationParameters类以帮助指定和检索导航参数,在导航期间,可以通过访问以下方法来传递导航参数:

  • INavigationAware接口的IsNavigationTargetOnNavigatedFromOnNavigatedTo方法中IsNavigationTargetOnNavigatedFromOnNavigatedTo中形参NavigationContext对象的NavigationParameters属性
  • IConfirmNavigationRequest接口的ConfirmNavigationRequest形参NavigationContext对象的NavigationParameters属性
  • 区域导航的INavigateAsync接口的RequestNavigate方法赋值给其形参navigationParameters
  • 导航日志IRegionNavigationJournal接口CurrentEntry属性的NavigationParameters类型的Parameters属性(下面会介绍导航日志)

这里我们CreateAccount页面注册完用户后询问是否需要用当前注册用户来作为登录LoginId,来演示传递导航参数,代码如下:

CreateAccountViewModel.cs(修改代码部分):

Copyprivate string _registeredLoginId;
public string RegisteredLoginId
{
    get { return _registeredLoginId; }
    set { SetProperty(ref _registeredLoginId, value); }
}

public bool IsUseRequest { get; set; }

void ExecuteVerityCommand(object parameter)
{
    if (!VerityRegister(parameter))
    {
        return;
    }
    this.IsUseRequest = true;
    MessageBox.Show("注册成功!");
    LoginMainContentCommand.Execute();
}   

public void ConfirmNavigationRequest(NavigationContext navigationContext, Action continuationCallback)
{
    if (!string.IsNullOrEmpty(RegisteredLoginId) && this.IsUseRequest)
    {
         if (MessageBox.Show("是否需要用当前注册的用户登录?", "Naviagte?", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
         {
              navigationContext.Parameters.Add("loginId", RegisteredLoginId);
         }
    }
    continuationCallback(true);
}


LoginMainContentViewModel.cs(修改代码部分):

Copypublic void OnNavigatedTo(NavigationContext navigationContext)
{
     MessageBox.Show("从CreateAccount导航到LoginMainContent");
            
     var loginId= navigationContext.Parameters["loginId"] as string;
     if (loginId!=null)
     {
         this.CurrentUser = new User() { LoginId=loginId};
     }
            
 }

效果如下:

四.导航日志#

Prism导航系统同样的和WPF导航系统一样,都支持导航日志,Prism是通过IRegionNavigationJournal接口来提供区域导航日志功能,

Copy    public interface IRegionNavigationJournal
    {
        bool CanGoBack { get; }

        bool CanGoForward { get; }

        IRegionNavigationJournalEntry CurrentEntry {get;}

        INavigateAsync NavigationTarget { get; set; }

        void GoBack();

        void GoForward();

        void RecordNavigation(IRegionNavigationJournalEntry entry, bool persistInHistory);

        void Clear();
    }

我们将在登录界面接入导航日志功能,代码如下:

LoginMainContent.xaml(前进箭头代码部分):

Copy
      
           
                 
            
      
      
           
      
 

BoolToVisibilityConverter.cs:

Copypublic class BoolToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
          if (value==null)
          {
              return DependencyProperty.UnsetValue;
          }
          var isCanExcute = (bool)value;
          if (isCanExcute)
          {
              return Visibility.Visible;
          }
          else
          {
              return Visibility.Hidden;
          }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
            throw new NotImplementedException();
    }
 }


LoginMainContentViewModel.cs(修改代码部分):

CopyIRegionNavigationJournal _journal;

private DelegateCommand _loginCommand;
public DelegateCommand LoginCommand =>
            _loginCommand ?? (_loginCommand = new DelegateCommand(ExecuteLoginCommand, CanExecuteGoForwardCommand));

private DelegateCommand _goForwardCommand;
public DelegateCommand GoForwardCommand =>
            _goForwardCommand ?? (_goForwardCommand = new DelegateCommand(ExecuteGoForwardCommand));

private void ExecuteGoForwardCommand()
{
    _journal.GoForward();
}

private bool CanExecuteGoForwardCommand(PasswordBox passwordBox)
{
    this.IsCanExcute=_journal != null && _journal.CanGoForward;
    return true;
}

public void OnNavigatedTo(NavigationContext navigationContext)
{
     //MessageBox.Show("从CreateAccount导航到LoginMainContent");
     _journal = navigationContext.NavigationService.Journal;

     var loginId= navigationContext.Parameters["loginId"] as string;
     if (loginId!=null)
     {
                this.CurrentUser = new User() { LoginId=loginId};
     }
     LoginCommand.RaiseCanExecuteChanged();
}

CreateAccountViewModel.cs(修改代码部分):

CopyIRegionNavigationJournal _journal;

private DelegateCommand _goBackCommand;
public DelegateCommand GoBackCommand =>
            _goBackCommand ?? (_goBackCommand = new DelegateCommand(ExecuteGoBackCommand));

void ExecuteGoBackCommand()
{
     _journal.GoBack();
}

 public void OnNavigatedTo(NavigationContext navigationContext)
 {
     //MessageBox.Show("从LoginMainContent导航到CreateAccount");
     _journal = navigationContext.NavigationService.Journal;
 }

效果如下:

选择退出导航日志#

如果不打算将页面在导航过程中不加入导航日志,例如LoginMainContent页面,可以通过实现IJournalAware并从PersistInHistory()返回false

Copy    public class LoginMainContentViewModel : IJournalAware
    {
        public bool PersistInHistory() => false;
    }   

五.小结:#

prism的导航系统可以跟wpf导航并行使用,这是prism官方文档也支持的,因为prism的导航系统是基于区域的,不依赖于wpf,不过更推荐于单独使用prism的导航系统,因为在MVVM模式下更灵活,支持依赖注入,通过区域管理器能够更好的管理视图View,更能适应复杂应用程序需求,wpf导航系统不支持依赖注入模式,也依赖于Frame元素,而且在导航过程中也是容易强依赖View部分,下一篇将会讲解Prism的对话框服务

六.源码#

最后,附上整个demo的源代码:PrismDemo源码


作者: RyzenAdorer

出处:
https://www.cnblogs.com/ryzen/p/12703914.html

相关推荐

Java七大热门技术框架源码解析(25章) 完结

获课》aixuetang.xyz/5699/Hibernate与MyBatis源码级PK:ORM框架的两种哲学在Java持久层框架领域,Hibernate与MyBatis代表了两种截然不同的设计哲学。...

【25章】Java七大热门技术框架源码解析

获课》aixuetang.xyz/5699/Java高级面试:七大框架源码精讲与实战解析在当今Java技术生态中,对主流框架源码的深入理解已成为高级开发者面试的核心竞争力。掌握Spring、MyBat...

饿了么董事长吴泽明兼任CEO,韩鎏分管即时物流中心

饿了么调整组织架构。2月11日,饿了么董事长吴泽明(花名:范禹)通过公司全员信宣布饿了么最新组织调整:即日起,吴泽明将兼任饿了么CEO,韩鎏(花名:昊宸)专注分管即时物流中心,继续向吴泽明汇报。吴泽明...

饿了么100%迁至阿里云,快速扩容可支持1亿人同时点单

来源:环球网6月17日,记者获悉,饿了么已完成100%上云,所有业务系统、数据库设施等均已迁移至阿里云。高峰期,饿了么可在阿里云上快速扩容,可以支持1亿人同时在线点单,这意味着饿了么的服务能力再次全面...

饿了么组织架构调整:董事长吴泽明兼任CEO 韩鎏专注即时物流中心管理

近日,饿了么董事长吴泽明(花名:范禹)通过公司全员信宣布饿了么最新组织调整:即日起,吴泽明将兼任饿了么CEO,韩鎏(花名:昊宸)专注分管即时物流中心,继续向吴泽明汇报。吴泽明在内部信中表示,考虑即时物...

饿了么组织架构调整:董事长吴泽明兼任CEO

Tech星球2月11日消息,据新浪科技报道,今日饿了么董事长吴泽明(花名:范禹)通过公司全员信宣布饿了么最新组织调整:即日起,吴泽明将兼任饿了么CEO,韩鎏(花名:昊宸)专注分管即时物流中心,继续向吴...

饿了么又调整了组织架构,董事长吴泽明兼任CEO

2月11日,饿了么董事长,花名为范禹的吴泽明,通过公司全员信宣布最新组织调整:从即日起,吴泽明将兼任饿了么CEO。公司原CEO,花名为昊宸的韩鎏今后专注分管即时物流中心,继续向吴泽明汇报。在内部信中,...

SpringBoot项目快速开发框架JeecgBoot——Web处理!

Web处理JeecgBoot框架主要用于Web开发领域。下面介绍JeecgBoot在Web开发中的常用功能,如控制器、登录、系统菜单、权限模块的角色管理和用户管理。首先启动后台项目,将其导入IDE...

腾讯即将开源Kuikly:基于Kotlin的纯原生跨端解决方案

IT之家3月4日消息,腾讯日前在端服务网站发布预告,即将开源Kuikly跨端开发框架。预告海报介绍称,Kuikly是基于KotlinKMM技术、客户端开发友好的全新跨端解决方案,可...

Python构建MCP服务器完整教程:5步打造专属AI工具调用系统

模型控制协议(ModelControlProtocol,MCP)是一种专为实现AI代理与工具解耦而设计的通信协议,为AI驱动应用程序的开发提供了高度的灵活性和模块化架构。通过MCP服务器,AI代...

Python3使用diagrams生成架构图(python模块制作)

目录技术背景diagrams的安装基础逻辑关系图组件簇的定义总结概要参考链接技术背景对于一个架构师或者任何一个软件工程师而言,绘制架构图都是一个比较值得学习的技能。这就像我们学习的时候整理的一些Xmi...

Python 失宠!Hugging Face 用 Rust 新写了一个 ML框架,现已低调开源

大数据文摘受权转载自AI前线整理|褚杏娟近期,HuggingFace低调开源了一个重磅ML框架:Candle。Candle一改机器学习惯用Python的做法,而是Rust编写,重...

Python Web 框架(Python Web 框架)

Tornado、Flask、Django三个PythonWeb框架的主要区别和适用场景:特点/框架TornadoFlaskDjango类型异步非阻塞Web服务器和框架轻量级微框架全功能...

构建并发布你的自定义 Python 包(python如何创建自定义模块)

Python让你可以重用代码,并将代码分享给他人以节省时间和精力。所以,当你编写了一些方便的脚本,希望你的同事或其他人也能使用时,接下来该怎么做呢?这篇文章就来解决打包和分发的问题。我们将专注于将你...

Python 应用开发框架 BeeWare 简明实用教程

1.BeeWare简介BeeWare是一个Python框架,用于开发跨平台原生应用。它支持Android、iOS、Windows、macOS和Linux,并提供原生用户体验。2.安装B...

取消回复欢迎 发表评论: