This article comes from reprint
Original author: RyzenAdorer
Original title: . NET Core 3 WPF MVVM Framework Prism Series Navigation System
Original link: www.cnblogs.com/ryzen/p/12703914.html
This article will introduce how to use the MVVM framework Prism region-based navigation system in the. NET Core3 environment
Before we talk about the Prism navigation system, let's take a look at an example. I created a login interface in the previous demo project:

Did we initially imagine using the navigation system included in WPF to jump pages through Frame and Page, and then use GoBack and GoForward in the navigation log to achieve backward and forward? In fact, this was achieved by using Prism's navigation framework. Let's take a look at how to implement this function in Prism's MVVM mode
1. regional navigation
We introduced Prism regional management in the previous article, and Prism's navigation system is also region-based. First, let's look at how to navigate in regions
1.1. Registration area
LoginWindow.xaml:
<Window
x:Class="PrismMetroSample.Shell.Views.Login.LoginWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PrismMetroSample.Shell.Views.Login"
xmlns:region="clr-namespace:PrismMetroSample.Infrastructure.Constants;assembly=PrismMetroSample.Infrastructure"
mc:Ignorable="d"
xmlns:prism="http://prismlibrary.com/"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Height="600"
Width="400"
prism:ViewModelLocator.AutoWireViewModel="True"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
Icon="pack://application:,,,/PrismMetroSample.Infrastructure;Component/Assets/Photos/Home, homepage, menu.png"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoginLoadingCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<ContentControl
prism:RegionManager.RegionName="{x:Static region:RegionNames.LoginContentRegion}"
Margin="5"
/>
</Grid>
</Window>
1.2. Registration Navigation
App.cs:
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<IMedicineSerivce, MedicineSerivce>();
containerRegistry.Register<IPatientService, PatientService>();
containerRegistry.Register<IUserService, UserService>();
//注册全局命令
containerRegistry.RegisterSingleton<IApplicationCommands, ApplicationCommands>();
containerRegistry.RegisterInstance<IFlyoutService>(Container.Resolve<FlyoutService>());
//注册导航
containerRegistry.RegisterForNavigation<LoginMainContent>();
containerRegistry.RegisterForNavigation<CreateAccount>();
}
1.3. regional navigation
LoginWindowViewModel.cs:
public 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:
public 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;
}
}
The effects are as follows:

Here we can see that we call RegionMannager's RequestNavigate method. In fact, this way it doesn't explain well that it is based on the region. If we change it to the following writing, it may be better understood:
//在LoginContentRegion区域导航到LoginMainContent
_regionManager.RequestNavigate(RegionNames.LoginContentRegion, "LoginMainContent");
replaced by
//在LoginContentRegion区域导航到LoginMainContent
IRegion region = _regionManager.Regions[RegionNames.LoginContentRegion];
region.RequestNavigate("LoginMainContent");
In fact, RegionMannager's RequestNavigation source code is also roughly implemented, which is a method to tune Region's RequestNavigation, and Region's navigation implements an INavigateAsync interface:
public interface INavigateAsync
{
void RequestNavigate(Uri target, Action<NavigationResult> navigationCallback);
void RequestNavigate(Uri target, Action<NavigationResult> navigationCallback, NavigationParameters navigationParameters);
}
We can see that there are three formal parameters of the RequestNavigate method:
- target: Indicates the page Uri to be navigated
- navigationCallback: Callback method after navigation
- navigationParameters: Navigation transfer parameters (explained in detail below)
Then we add the callback method to the above:
//在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页面失败");
}
}
The effects are as follows:

2. View and ViewModel participate in the navigation process
2.1. INavigationAware
We often need to process some logic when navigating between two pages. For example, when the LoginMainContent page navigates to the CreateAccount page, the page data must be saved when LoginMainContent exits the page. The processing logic at the time of navigating to the CreateAccount page (for example, obtaining information from the LoginMainContent page), Prism's navigation system uses an INavigationAware interface:
public interface INavigationAware : Object
{
Void OnNavigatedTo(NavigationContext navigationContext);
Boolean IsNavigationTarget(NavigationContext navigationContext);
Void OnNavigatedFrom(NavigationContext navigationContext);
}
- OnNavigatedFrom: triggered before navigation, generally used to save data on the page
- OnNavigatedTo: Trigger the destination page after navigation, generally used to initialize or accept the delivery parameters of the previous page
- IsNavigationTarget: True reuses the View instance, and Flase instantiates it every time it navigates to the page
We use code to demonstrate these three methods:
LoginMainContentViewModel.cs:
public 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:
public 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");
}
}
The effects are as follows:

Change IsNavigationTarget to false:
public class LoginMainContentViewModel : BindableBase, INavigationAware
{
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return false;
}
}
public class CreateAccountViewModel : BindableBase,INavigationAware
{
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return false;
}
}
The effects are as follows:

We will find that the data from the LoginMainContent and CreateAccount pages is missing. This is because when the second navigation to the page, when IsNavigationTarget is false, the View will be re-instanced, causing the ViewModel to be reloaded, so all data is cleared.
2.2. IRegionMemberLifetime
At the same time, Prism can also control the life cycle of the view of the area through the KeepAlive Boolean attribute of the IRegionMemberLifetime interface. We mentioned in the previous article about the area manager that when a view is added to the area, things like ContentControl can display an active view separately, and the view can be activated and deactivated through the Activate and Deactivate methods of the Region. Things like ItemsControl can display multiple active views at the same time. You can control the addition of active views and invalid views through the Add and Remove methods of Region. When the KeepAlive of a view is false and the Region activates another view, the instance of the view will be removed from the region. Why don't we explain this interface in the Region Manager? Because when navigation, the Activate and Deactivate of Region are also triggered, and when there is an IRegionMember Lifetime interface, the Add and Remove methods of Region are triggered. You can take a look at the source code of Prism RegionMember Lifetime Behavior
We implement the IRegionMemberLifetime interface with LoginMainContentViewModel, set KeepAlive to false, and set IsNavigationTarget to true
LoginMainContentViewModel.cs:
public 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");
}
}
The effects are as follows:

We will find that just like the case where the IRegionMemberLifetime interface is not implemented and IsNavigationTarget is set to false, when KeepAlive is false, we know through the breakpoint that the IsNavigationTarget method will not be triggered when re-navigating back to the LoginMainContent page. Therefore, we can know that the judgment order is: KeepAlive -->IsNavigationTarget
2.3. IConfirmNavigationRequest
Prism's navigation system also supports the interactive requirement of allowing navigation before re-navigation. Here, after registering the user in CreateAccount, we ask if we need to navigate back to the LoginMainContent page. The code is as follows:
CreateAccountViewModel.cs:
public 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<bool> continuationCallback)
{
var result = false;
if (MessageBox.Show("是否需要导航到LoginMainContent页面?", "Naviagte?",MessageBoxButton.YesNo) ==MessageBoxResult.Yes)
{
result = true;
}
continuationCallback(result);
}
}
The effects are as follows:

3. Passing parameters during navigation
Prism provides the NavigationParameters class to help specify and retrieve navigation parameters, which can be passed during navigation by accessing the following methods:
- The NavigationParameters property of the NavigationContext object in the IsNavigationTarget, OnNavigatedFrom and OnNavigatedTo methods of the INavigationAware interface
- ConfirmNavigationRequest parameter of the IConfirmNavigationRequest interface NavigationParameters property of the NavigationContext object
- The RequestNavigation method of the INavigateAsync interface of area navigation assigns its formal parameters navigationParameters
- Navigation Log Parameters property of type NavigationParameters of the CurrentEntry property of the IRegionNavigationJournal interface (Navigation logs are described below)
Here, after registering users on the CreateAccount page, we ask if we need to use the currently registered user as the login LoginId to demonstrate the transfer of navigation parameters. The code is as follows:
CreateAccountViewModel.cs (modify the code section):
private 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<bool> continuationCallback)
{
if (!string.IsNullOrEmpty(RegisteredLoginId) && this.IsUseRequest)
{
if (MessageBox.Show("是否需要用当前注册的用户登录?", "Naviagte?", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
navigationContext.Parameters.Add("loginId", RegisteredLoginId);
}
}
continuationCallback(true);
}
LoginMainContentViewModel.cs (modify the code section):
public void OnNavigatedTo(NavigationContext navigationContext)
{
MessageBox.Show("从CreateAccount导航到LoginMainContent");
var loginId= navigationContext.Parameters["loginId"] as string;
if (loginId!=null)
{
this.CurrentUser = new User() { LoginId=loginId};
}
}
The effects are as follows:

4. navigation log
The Prism navigation system, like the WPF navigation system, supports navigation logs. Prism provides the area navigation log function through the IRegionNavigationJournal interface.
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();
}
We will access the navigation log function on the login interface, with the code as follows:
LoginMainContent.xaml(forward arrow code section):
<TextBlock Width="30" Height="30" HorizontalAlignment="Right" Text="" FontWeight="Bold" FontFamily="pack://application:,,,/PrismMetroSample.Infrastructure;Component/Assets/Fonts/#iconfont" FontSize="30" Margin="10" Visibility="{Binding IsCanExcute,Converter={StaticResource boolToVisibilityConverter}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding GoForwardCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#F9F9F9"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
BoolToVisibilityConverter.cs:
public 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 (modify the code section):
IRegionNavigationJournal _journal;
private DelegateCommand<PasswordBox> _loginCommand;
public DelegateCommand<PasswordBox> LoginCommand =>
_loginCommand ?? (_loginCommand = new DelegateCommand<PasswordBox>(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 (modify the code section):
IRegionNavigationJournal _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;
}
The effects are as follows:

4.1 Select to exit the navigation log
If you don't plan to leave the page without adding the navigation log during navigation, such as the LoginMainContent page, you can implement IJournalAware and return false from PersistInHistory ()
public class LoginMainContentViewModel : IJournalAware
{
public bool PersistInHistory() => false;
}
5. summary
Prism's navigation system can be used in parallel with wpf navigation, which is also supported by the official prrism documentation, because the prrism navigation system is region-based and does not rely on wpf. However, it is more recommended to use the prism navigation system alone, because it is more flexible in MVVM mode and supports dependency injection. View views can be better managed through the zone manager, which can better adapt to the needs of complex applications. The wpf navigation system does not support dependency injection mode. It also relies on the Frame element, and it is easy to rely heavily on the View part during the navigation process. The next article will explain Prism 'dialog box service
6. source code
最后,附上整个 demo 的源代码:PrismDemo 源码