(5/7).NET Core 3 WPF MVVM Framework Prism Series: Region Manager

(5/7).NET Core 3 WPF MVVM Framework Prism Series: Region Manager

How to use the region manager of the MVVM framework Prism for View management in .NET Core 3 environment.

Last updated 6/11/2023 12:21 AM
RyzenAdorer
11 min read
Category
WPF
Topic
WPF MVVM Framework Prism Series
Tags
.NET C# WPF Prism MVVM

This article is reproduced from a reprint.

Original author: RyzenAdorer

Original title: .NET Core 3 WPF MVVM Framework Prism Series: Region Manager

Original link: https://www.cnblogs.com/ryzen/p/12605347.html

This article will introduce how to use the MVVM framework Prism's Region Manager for View management in the .NET Core 3 environment.

1. Region Manager

In our previous Prism series, we built a standard Prism project. This article will explain the use of the region manager in that project to better manage our Views. Similarly, let's take a look at the official model diagram:

Now we can understand the key points of how a RegionManager creates a region for a control:

  • The control that creates a Region must have a RegionAdapter.
  • The region depends on the control that has the RegionAdapter.

Later, I checked the official documentation and source code. By default, there are three RegionAdapters, and custom RegionAdapters are also supported. So I added some supplements to the official model diagram:

2. Region Creation and View Injection

Let's first look at the region division in our previous project, and how to create regions and inject Views into them:

We divided the main window into four regions:

  • ShowSearchPatientRegion: injected with the ShowSearchPatient view
  • PatientListRegion: injected with the PatientList view
  • FlyoutRegion: injected with the PatientDetail and SearchMedicine views
  • ShowSearchPatientRegion: injected with the ShowSearchPatient view (Note: This appears to be a duplicate in the list, but the original text listed it twice; we'll keep it as is.)

In Prism, there are two ways to implement region creation and view injection:

  1. ViewDiscovery
  2. ViewInjection

2.1. ViewDiscovery

Let's extract the code for creating and injecting the PatientListRegion (more details can be found in the demo source code):

MainWindow.xaml:

<ContentControl
  Grid.Row="2"
  prism:RegionManager.RegionName="PatientListRegion"
  Margin="10"
/>

This is equivalent to the backend code in MainWindow.cs:

RegionManager.SetRegionName(ContentControl, "PatientListRegion");

PatientModule.cs:

public class PatientModule : IModule
 {
    public void OnInitialized(IContainerProvider containerProvider)
    {
         var regionManager = containerProvider.Resolve<IRegionManager>();
         //PatientList
         regionManager.RegisterViewWithRegion(RegionNames.PatientListRegion, typeof(PatientList));
         //PatientDetail-Flyout
         regionManager.RegisterViewWithRegion(RegionNames.FlyoutRegion, typeof(PatientDetail));

     }

    public void RegisterTypes(IContainerRegistry containerRegistry)
    {

    }
 }

2.2. ViewInjection

We use the ViewInjection method to inject the PatientList view in the Loaded event of the MainWindow form.

MainWindow.xaml:

<i:Interaction.Triggers>      <i:EventTrigger EventName="Loaded">
          <i:InvokeCommandAction Command="{Binding LoadingCommand}"/>
       /i:EventTrigger>
  </i:Interaction.Triggers>

MainWindowViewModel.cs:

private IRegionManager _regionManager;
private IRegion _paientListRegion;
private PatientList _patientListView;

private DelegateCommand _loadingCommand;
public DelegateCommand LoadingCommand =>
     _loadingCommand ?? (_loadingCommand = new DelegateCommand(ExecuteLoadingCommand));

void ExecuteLoadingCommand()
{
     _regionManager = CommonServiceLocator.ServiceLocator.Current.GetInstance<IRegionManager>();
     _paientListRegion = _regionManager.Regions[RegionNames.PatientListRegion];
     _patientListView = CommonServiceLocator.ServiceLocator.Current.GetInstance<PatientList>();
     _paientListRegion.Add(_patientListView);

 }

We can clearly see the difference between the two methods. The ViewDiscovery method automatically instantiates and loads the view, while the ViewInjection method allows manual control over when to inject and load the view (the example above uses the Loaded event). The official recommended usage scenarios for each are as follows:

ViewDiscovery:

  • Need or require automatic view loading.
  • A single instance of the view will be loaded into the region.

ViewInjection:

  • Need explicit or programmatic control over when to create and display the view, or need to remove the view from the region.
  • Need to display multiple instances of the same view in the region, where each view instance is bound to different data.
  • Need to control which instance of the region the view is added to.
  • The application uses the navigation API (to be covered later).

3. Activating and Deactivating Views

3.1. Activate and Deactivate

First, we need to control the activation of the PatientList and MedicineMainContent views. Here's the code:

MainWindow.xaml:

<StackPanel Grid.Row="1">
  <button
    Content="Load MedicineModule"
    FontSize="25"
    Margin="5"
    Command="{Binding LoadMedicineModuleCommand}"
  />
  <UniformGrid Margin="5">
    <button
      Content="ActivePaientList"
      Margin="5"
      Command="{Binding ActivePaientListCommand}"
    />
    <button
      Content="DeactivePaientList"
      Margin="5"
      Command="{Binding DeactivePaientListCommand}"
    />
    <button
      Content="ActiveMedicineList"
      Margin="5"
      Command="{Binding ActiveMedicineListCommand}"
    />
    <button
      Content="DeactiveMedicineList"
      Margin="5"
      Command="{Binding DeactiveMedicineListCommand}"
    />
  </UniformGrid>
</StackPanel>

<ContentControl
  Grid.Row="2"
  prism:RegionManager.RegionName="PatientListRegion"
  Margin="10"
/>
<ContentControl
  Grid.Row="3"
  prism:RegionManager.RegionName="MedicineMainContentRegion"
/>

MainWindowViewModel.cs:

  private IRegionManager _regionManager;
  private IRegion _paientListRegion;
  private IRegion _medicineListRegion;
  private PatientList _patientListView;
  private MedicineMainContent _medicineMainContentView;

  private bool _isCanExcute = false;
  public bool IsCanExcute
  {
     get { return _isCanExcute; }
     set { SetProperty(ref _isCanExcute, value); }
  }

  private DelegateCommand _loadingCommand;
  public DelegateCommand LoadingCommand =>
      _loadingCommand ?? (_loadingCommand = new DelegateCommand(ExecuteLoadingCommand));

  private DelegateCommand _activePaientListCommand;
  public DelegateCommand ActivePaientListCommand =>
      _activePaientListCommand ?? (_activePaientListCommand = new DelegateCommand(ExecuteActivePaientListCommand));

  private DelegateCommand _deactivePaientListCommand;
  public DelegateCommand DeactivePaientListCommand =>
      _deactivePaientListCommand ?? (_deactivePaientListCommand = new DelegateCommand(ExecuteDeactivePaientListCommand));

   private DelegateCommand _activeMedicineListCommand;
   public DelegateCommand ActiveMedicineListCommand =>
      _activeMedicineListCommand ?? (_activeMedicineListCommand = new DelegateCommand(ExecuteActiveMedicineListCommand)
      .ObservesCanExecute(() => IsCanExcute));

   private DelegateCommand _deactiveMedicineListCommand;
   public DelegateCommand DeactiveMedicineListCommand =>
       _deactiveMedicineListCommand ?? (_deactiveMedicineListCommand = new DelegateCommand(ExecuteDeactiveMedicineListCommand)
      .ObservesCanExecute(() => IsCanExcute));

   private DelegateCommand _loadMedicineModuleCommand;
   public DelegateCommand LoadMedicineModuleCommand =>
       _loadMedicineModuleCommand ?? (_loadMedicineModuleCommand = new DelegateCommand(ExecuteLoadMedicineModuleCommand));

 /// <summary>
 /// Form loading event
 /// </summary>
 void ExecuteLoadingCommand()
 {
      _regionManager = CommonServiceLocator.ServiceLocator.Current.GetInstance<IRegionManager>();
      _paientListRegion = _regionManager.Regions[RegionNames.PatientListRegion];
      _patientListView = CommonServiceLocator.ServiceLocator.Current.GetInstance<PatientList>();
      _paientListRegion.Add(_patientListView);
      _medicineListRegion = _regionManager.Regions[RegionNames.MedicineMainContentRegion];
 }

  /// <summary>
  /// Deactivate medicineMainContent view
  /// </summary>
  void ExecuteDeactiveMedicineListCommand()
  {
      _medicineListRegion.Deactivate(_medicineMainContentView);
  }

  /// <summary>
  /// Activate medicineMainContent view
  /// </summary>
  void ExecuteActiveMedicineListCommand()
  {
      _medicineListRegion.Activate(_medicineMainContentView);
  }

  /// <summary>
  /// Deactivate patientList view
  /// </summary>
  void ExecuteDeactivePaientListCommand()
  {
       _paientListRegion.Deactivate(_patientListView);
  }

  /// <summary>
  /// Activate patientList view
  /// </summary>
  void ExecuteActivePaientListCommand()
  {
       _paientListRegion.Activate(_patientListView);
  }

  /// <summary>
  /// Load MedicineModule
  /// </summary>
  void ExecuteLoadMedicineModuleCommand()
  {
       _moduleManager.LoadModule("MedicineModule");
       _medicineMainContentView = (MedicineMainContent)_medicineListRegion.Views
            .Where(t => t.GetType() == typeof(MedicineMainContent)).FirstOrDefault();
       this.IsCanExcute = true;
   }

Effect:

3.2. Monitoring View Activation Status

Prism also supports monitoring the activation status of views by implementing the IActiveAware interface in the View. We'll use monitoring the activation status of the MedicineMainContent view as an example:

MedicineMainContentViewModel.cs:

public class MedicineMainContentViewModel : BindableBase,IActiveAware
 {
     public event EventHandler IsActiveChanged;

     bool _isActive;
     public bool IsActive
     {
         get { return _isActive; }
         set
         {
             _isActive = value;
             if (_isActive)
             {
                 MessageBox.Show("View activated");
             }
             else
             {
                 MessageBox.Show("View deactivated");
             }
             IsActiveChanged?.Invoke(this, new EventArgs());
          }
      }

  }

3.3. Add and Remove

The above example uses ContentControl. Now we'll use an ItemsControl example with the following code:

MainWindow.xaml:

<metro:MetroWindow.RightWindowCommands>
  <metro:WindowCommands x:Name="rightWindowCommandsRegion" />
</metro:MetroWindow.RightWindowCommands>

MainWindow.cs:

public MainWindow()
 {
    InitializeComponent();
    var regionManager= ServiceLocator.Current.GetInstance<IRegionManager>();
    if (regionManager != null)
    {
       SetRegionManager(regionManager, this.flyoutsControlRegion, RegionNames.FlyoutRegion);
       // Create WindowCommands control region
       SetRegionManager(regionManager, this.rightWindowCommandsRegion, RegionNames.ShowSearchPatientRegion);
    }
 }

 void SetRegionManager(IRegionManager regionManager, DependencyObject regionTarget, string regionName)
 {
     RegionManager.SetRegionName(regionTarget, regionName);
     RegionManager.SetRegionManager(regionTarget, regionManager);
 }

ShowSearchPatient.xaml:

<StackPanel
  x:Class="PrismMetroSample.MedicineModule.Views.ShowSearchPatient"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:prism="http://prismlibrary.com/"
  xmlns:const="clr-namespace:PrismMetroSample.Infrastructure.Constants;assembly=PrismMetroSample.Infrastructure"
  Orientation="Horizontal"
  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
  prism:ViewModelLocator.AutoWireViewModel="True"
>
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
      <i:InvokeCommandAction Command="{Binding ShowSearchLoadingCommand}" />
    </i:EventTrigger>
  </i:Interaction.Triggers>
  <CheckBox IsChecked="{Binding IsShow}" />
  <button
    Command="{Binding ApplicationCommands.ShowCommand}"
    CommandParameter="{x:Static const:FlyoutNames.SearchMedicineFlyout}"
  >
    <StackPanel Orientation="Horizontal">
      <image
        Height="20"
        Source="pack://application:,,,/PrismMetroSample.Infrastructure;Component/Assets/Photos/按钮.png"
      />
      <TextBlock
        Text="Show"
        FontWeight="Bold"
        FontSize="15"
        VerticalAlignment="Center"
      />
    </StackPanel>
  </button>
</StackPanel>

ShowSearchPatientViewModel.cs:

 private IApplicationCommands _applicationCommands;
 private readonly IRegionManager _regionManager;
 private ShowSearchPatient _showSearchPatientView;
 private IRegion _region;

 public IApplicationCommands ApplicationCommands
 {
      get { return _applicationCommands; }
      set { SetProperty(ref _applicationCommands, value); }
 }

 private bool _isShow=true;
 public bool IsShow
 {
      get { return _isShow=true; }
      set
      {
          SetProperty(ref _isShow, value);
          if (_isShow)
          {
               ActiveShowSearchPatient();
          }
          else
          {
               DeactiveShowSearchPaitent();
          }
      }
 }

 private DelegateCommand _showSearchLoadingCommand;
 public DelegateCommand ShowSearchLoadingCommand =>
         _showSearchLoadingCommand ?? (_showSearchLoadingCommand = new DelegateCommand(ExecuteShowSearchLoadingCommand));

 void ExecuteShowSearchLoadingCommand()
 {
        _region = _regionManager.Regions[RegionNames.ShowSearchPatientRegion];
        _showSearchPatientView = (ShowSearchPatient)_region.Views
            .Where(t => t.GetType() == typeof(ShowSearchPatient)).FirstOrDefault();
 }


  public ShowSearchPatientViewModel(IApplicationCommands applicationCommands,IRegionManager regionManager)
  {
        this.ApplicationCommands = applicationCommands;
        _regionManager = regionManager;
  }

  /// <summary>
  /// Activate view
  /// </summary>
  private void ActiveShowSearchPatient()
  {
       if (!_region.ActiveViews.Contains(_showSearchPatientView))
       {
           _region.Add(_showSearchPatientView);
       }
  }

  /// <summary>
  /// Deactivate view
  /// </summary>
   private async void DeactiveShowSearchPaitent()
   {
        _region.Remove(_showSearchPatientView);
        await Task.Delay(2000);
        IsShow = true;
   }

Effect:

The inheritance chain for WindowCommands here is: WindowCommands <-- ToolBar <-- HeaderedItemsControl <-- ItemsControl. Since Prism's default adapter includes ItemsControlRegionAdapter, its subclasses also inherit this behavior.

Key points to summarize:

  • When modularizing, views are injected into regions only after the module is loaded (refer to the loading order of the MedicineModule view).
  • The ContentControl, due to its Content property only displaying one item, can control which view to display within its region using the Activate and Deactivate methods. This behavior is controlled by the ContentControlRegionAdapter.
  • For ItemsControl and its subclasses, since they display a collection of views, all views in the collection are active by default. You cannot use Activate and Deactivate to control them (it will cause an error). Instead, use Add and Remove to control which views are displayed. This behavior is controlled by the ItemsControlRegionAdapter.
  • The Selector control is not mentioned here, but since it also inherits from ItemsControl, its SelectorRegionAdapter works similarly to the ItemsControlRegionAdapter.
  • You can monitor the activation status of a view by implementing the IActiveAware interface.

4. Custom Region Adapter

In the introduction to the overall region manager model, we mentioned that Prism has three default region adapters: ItemsControlRegionAdapter, ContentControlRegionAdapter, and SelectorRegionAdapter, and it supports custom region adapters. Now let's create a custom adapter.

4.1. Creating a Custom Adapter

Create a new class UniformGridRegionAdapter.cs:

public class UniformGridRegionAdapter : RegionAdapterBase<UniformGrid>
{
    public UniformGridRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory) : base(regionBehaviorFactory)
    {

    }

    protected override void Adapt(IRegion region, UniformGrid regionTarget)
    {
        region.Views.CollectionChanged += (s, e) =>
        {
          if (e.Action==System.Collections.Specialized.NotifyCollectionChangedAction.Add)
          {
              foreach (FrameworkElement element in e.NewItems)
              {
                   regionTarget.Children.Add(element);
              }
          }
        };
    }

    protected override IRegion CreateRegion()
    {
        return new AllActiveRegion();
    }
 }

4.2. Register Mapping

App.cs:

protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings)
{
    base.ConfigureRegionAdapterMappings(regionAdapterMappings);
    // Register adapter mapping for UniformGrid control
    regionAdapterMappings.RegisterMapping(typeof(UniformGrid),Container.Resolve<UniformGridRegionAdapter>());
}

4.3. Create Region for Control

MainWindow.xaml:

<UniformGrid
  Margin="5"
  prism:RegionManager.RegionName="UniformContentRegion"
  Columns="2"
>
  <button
    Content="ActivePaientList"
    Margin="5"
    Command="{Binding ActivePaientListCommand}"
  />
  <button
    Content="DeactivePaientList"
    Margin="5"
    Command="{Binding DeactivePaientListCommand}"
  />
  <button
    Content="ActiveMedicineList"
    Margin="5"
    Command="{Binding ActiveMedicineListCommand}"
  />
  <button
    Content="DeactiveMedicineList"
    Margin="5"
    Command="{Binding DeactiveMedicineListCommand}"
  />
</UniformGrid>

4.4. Inject Views into Region

Here we use the ViewInjection method:

MainWindowViewModel.cs

void ExecuteLoadingCommand()
  {
         _regionManager = CommonServiceLocator.ServiceLocator.Current.GetInstance<IRegionManager>();

         var uniformContentRegion = _regionManager.Regions["UniformContentRegion"];
         var regionAdapterView1 = CommonServiceLocator.ServiceLocator.Current.GetInstance<RegionAdapterView1>();
         uniformContentRegion.Add(regionAdapterView1);
         var regionAdapterView2 = CommonServiceLocator.ServiceLocator.Current.GetInstance<RegionAdapterView2>();
         uniformContentRegion.Add(regionAdapterView2);
  }

Effect:

We can see that after creating a region adapter for UniformGrid and registering it, we can also create a region for the UniformGrid control and inject views to display. Without the region adapter, an error would occur. In the next article, we will discuss the Prism navigation system based on regions.

5. Source Code

Finally, here is the full demo source code: PrismDemo Source Code

Keep Exploring

Related Reading

More Articles