本文來自轉載
原文作者:ryzenadorer
原文標題:.net core 3 wpf mvvm 框架 prism 系列之區域管理器
原文連結:https://www.cnblogs.com/ryzen/p/12605347.html
本文將居間如何在.net core3 環境下使用 mvvm 框架 prism 的使用區域管理器對於 view 的管理
1. 區域管理器
我們在之前的 prism 系列構建了一個標準式 prism 項目,這篇文章將會講解之前項目中用到的利用區域管理器更好的對我們的 view 進行管理,同樣的我們來看看官方給出的模型圖:

現在我們可以知道的是,大致一個區域管理器 regionmannager 對一個控制項創建區域的要點:
- 創建 region 的控制項必須包含一個 regionadapter 適配器
- region 是依賴在具有 regionadapter 控制項身上的
其實後來我去看了下官方的居間和源碼,默認 regionadapter 是有三個,且還支持自定義 regionadapter,因此在官方的模型圖之間我做了點補充:

2. 區域創建與視圖的注入
我們先來看看我們之前項目的區域的劃分,以及如何創建區域並且把 view 注入到區域中:

我們把整個主窗體劃分了四個區域:
- showsearchpatientregion:注入了 showsearchpatient 視圖
- patientlistregion:注入了 patientlist 視圖
- flyoutregion:注入了 patientdetail 和 searchmedicine 視圖
- showsearchpatientregion:注入了 showsearchpatient 視圖
在 prism 中,我們有兩種方式去實現區域創建和視圖注入:
- ViewDiscovery
- ViewInjection
2.1. ViewDiscovery
我們截取其中 patientlistregion 的創建和視圖注入的代碼(更仔細的可以去觀看 demo 源碼):
MainWindow.xaml:
<ContentControl
Grid.Row="2"
prism:RegionManager.RegionName="PatientListRegion"
Margin="10"
/>
這裡相當於在後台 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
我們在 mainwindow 窗體的 loaded 事件中使用 viewinjection 方式注入視圖 patientlist
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);
}
我們可以明顯的感覺到兩種方式的不同,viewdiscovery 方式是自動地實例化視圖並且加載出來,而 viewinjection 方式則是可以手動控制注入視圖和加載視圖的時機(上述例子是通過 loaded 事件),官方對於兩者的推薦使用場景如下:
ViewDiscovery:
- 需要或要求自動加載視圖
- 視圖的單個實例將加載到該區域中
ViewInjection:
- 需要顯式或編程控制何時創建和顯示視圖,或者您需要從區域中刪除視圖
- 需要在區域中顯示相同視圖的多個實例,其中每個視圖實例都綁定到不同的數據
- 需要控制添加視圖的區域的哪個實例
- 應用程式使用導航 api(後面會講到)
3. 激活與失效視圖
3.1. activate 和 deactivate
首先我們需要控制 patientlist 和 medicinemaincontent 兩個視圖的激活情況,上代碼:
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>
/// 窗体加载事件
/// </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>
/// 失效medicineMainContent视图
/// </summary>
void ExecuteDeactiveMedicineListCommand()
{
_medicineListRegion.Deactivate(_medicineMainContentView);
}
/// <summary>
/// 激活medicineMainContent视图
/// </summary>
void ExecuteActiveMedicineListCommand()
{
_medicineListRegion.Activate(_medicineMainContentView);
}
/// <summary>
/// 失效patientList视图
/// </summary>
void ExecuteDeactivePaientListCommand()
{
_paientListRegion.Deactivate(_patientListView);
}
/// <summary>
/// 激活patientList视图
/// </summary>
void ExecuteActivePaientListCommand()
{
_paientListRegion.Activate(_patientListView);
}
/// <summary>
/// 加载MedicineModule
/// </summary>
void ExecuteLoadMedicineModuleCommand()
{
_moduleManager.LoadModule("MedicineModule");
_medicineMainContentView = (MedicineMainContent)_medicineListRegion.Views
.Where(t => t.GetType() == typeof(MedicineMainContent)).FirstOrDefault();
this.IsCanExcute = true;
}
效果如下:

3.2.監控視圖激活狀態
prism 其中還支持監控視圖的激活狀態,是通過在 view 中繼承 iactiveaware 來實現的,我們以監控其中 medicinemaincontent 視圖的激活狀態為例子:
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("视图被激活了");
}
else
{
MessageBox.Show("视图失效了");
}
IsActiveChanged?.Invoke(this, new EventArgs());
}
}
}

3.3. add 和 remove
上述例子用的是 contentcontrol,我們再用一個 itemscontrol 的例子,代碼如下:
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);
//创建WindowCommands控件区域
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>
/// 激活视图
/// </summary>
private void ActiveShowSearchPatient()
{
if (!_region.ActiveViews.Contains(_showSearchPatientView))
{
_region.Add(_showSearchPatientView);
}
}
/// <summary>
/// 失效视图
/// </summary>
private async void DeactiveShowSearchPaitent()
{
_region.Remove(_showSearchPatientView);
await Task.Delay(2000);
IsShow = true;
}
效果如下:

這裡的 windowcommands 的繼承鏈為:windowcommands <-- toolbar <-- headereditemscontrol <--itemscontrol,因此由於 prism 默認的適配器有 itemscontrolregionadapter,因此其子類也繼承了其行為
這裡重點歸納一下:
- 當進行模塊化時,加載完模塊才會去注入視圖到區域(可參考 medicinemodule 視圖加載順序)
- contentcontrol 控制項由於 content 只能顯示一個,在其區域中可以通過 activate 和 deactivate 方法來控制顯示哪個視圖,其行為是由 contentcontrolregionadapter 適配器控制
- itemscontrol 控制項及其子控制項由於顯示一個集合視圖,默認全部集合視圖是激活的,這時候不能通過 activate 和 deactivate 方式來控制(會報錯),通過 add 和 remove 來控制要顯示哪些視圖,其行為是由 itemscontrolregionadapter 適配器控制
- 這裡沒講到 selector 控制項,因為也是繼承自 itemscontrol,因此其 selectorregionadapter 適配器和 itemscontrolregionadapter 適配器異曲同工
- 可以通過繼承 iactiveaware 接口來監控視圖激活狀態
4. 自定義區域適配器
我們在居間整個區域管理器模型圖中說過,prism 有三個默認的區域適配器:itemscontrolregionadapter,contentcontrolregionadapter,selectorregionadapter,且支持自定義區域適配器,現在我們來自定義一下適配器
4.1.創建自定義適配器
新建類 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.註冊映射
App.cs:
protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings)
{
base.ConfigureRegionAdapterMappings(regionAdapterMappings);
//为UniformGrid控件注册适配器映射
regionAdapterMappings.RegisterMapping(typeof(UniformGrid),Container.Resolve<UniformGridRegionAdapter>());
}
4.3.為控制項創建區域
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.為區域注入視圖
這裡用的是 viewinjection 方式:
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);
}
效果如圖:

我們可以看到我們為 uniformgrid 創建區域適配器,並且註冊後,也能夠為 uniformgrid 控制項創建區域,並且注入視圖顯示,如果沒有該區域適配器,則是會報錯,下一篇我們將會講解基於區域 region 的 prism 導航系統。
5. 源碼
最后,附上整个 demo 的源代码:PrismDemo 源码