.NET Core 3 WPF MVVM框架 Prism系列之區域管理器
阿新 • • 發佈:2020-04-01
本文將介紹如何在.NET Core3環境下使用MVVM框架Prism的使用區域管理器對於View的管理
## 一.區域管理器
我們在之前的Prism系列構建了一個標準式Prism專案,這篇文章將會講解之前專案中用到的利用區域管理器更好的對我們的View進行管理,同樣的我們來看看官方給出的模型圖:
![](https://img2020.cnblogs.com/blog/1294271/202003/1294271-20200331150607822-923107646.png)
現在我們可以知道的是,大致一個區域管理器RegionMannager對一個控制元件建立區域的要點:
- 建立Region的控制元件必須包含一個RegionAdapter介面卡
- region是依賴在具有RegionAdapter控制元件身上的
其實後來我去看了下官方的介紹和原始碼,預設RegionAdapter是有三個,且還支援自定義RegionAdapter,因此在官方的模型圖之間我做了點補充:
![](https://img2020.cnblogs.com/blog/1294271/202003/1294271-20200331150617806-226264999.png)
## 二.區域建立與檢視的注入
我們先來看看我們之前專案的區域的劃分,以及如何建立區域並且把View注入到區域中:
![](https://img2020.cnblogs.com/blog/1294271/202003/1294271-20200331150628801-1210659452.png)
我們把整個主窗體劃分了四個區域:
- **ShowSearchPatientRegion**:注入了ShowSearchPatient檢視
- **PatientListRegion**:注入了PatientList檢視
- **FlyoutRegion**:注入了PatientDetail和SearchMedicine檢視
- **ShowSearchPatientRegion**:注入了ShowSearchPatient檢視
在Prism中,我們有兩種方式去實現區域建立和檢視注入:
1. **ViewDiscovery**
2. **ViewInjection**
### 1.**ViewDiscovery**
我們擷取其中**PatientListRegion**的建立和檢視注入的程式碼(更仔細的可以去觀看demo原始碼):
MainWindow.xaml:
```xaml
```
這裡相當於在後臺MainWindow.cs:
```c#
RegionManager.SetRegionName(ContentControl, "PatientListRegion");
```
PatientModule.cs:
```c#
public class PatientModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve();
//PatientList
regionManager.RegisterViewWithRegion(RegionNames.PatientListRegion, typeof(PatientList));
//PatientDetail-Flyout
regionManager.RegisterViewWithRegion(RegionNames.FlyoutRegion, typeof(PatientDetail));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
```
### 2.**ViewInjection**
我們在MainWindow窗體的Loaded事件中使用**ViewInjection**方式注入檢視PatientList
MainWindow.xaml:
```xaml
/i:EventTrigger>
```
MainWindowViewModel.cs:
```c#
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();
_paientListRegion = _regionManager.Regions[RegionNames.PatientListRegion];
_patientListView = CommonServiceLocator.ServiceLocator.Current.GetInstance();
_paientListRegion.Add(_patientListView);
}
```
我們可以明顯的感覺到兩種方式的不同,**ViewDiscovery**方式是自動地例項化檢視並且加載出來,而**ViewInjection**方式則是可以手動控制注入檢視和載入檢視的時機(上述例子是通過Loaded事件),官方對於兩者的推薦使用場景如下:
**ViewDiscovery**:
- 需要或要求自動載入檢視
- 檢視的單個例項將載入到該區域中
**ViewInjection**:
- 需要顯式或程式設計控制何時建立和顯示檢視,或者您需要從區域中刪除檢視
- 需要在區域中顯示相同檢視的多個例項,其中每個檢視例項都繫結到不同的資料
- 需要控制新增檢視的區域的哪個例項
- 應用程式使用導航API(後面會講到)
## 三.啟用與失效檢視
### Activate和Deactivate
首先我們需要控制PatientList和MedicineMainContent兩個檢視的啟用情況,上程式碼:
MainWindow.xaml:
```xaml
```
MainWindowViewModel.cs:
```c#
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));
///
/// 窗體載入事件
///
void ExecuteLoadingCommand()
{
_regionManager = CommonServiceLocator.ServiceLocator.Current.GetInstance();
_paientListRegion = _regionManager.Regions[RegionNames.PatientListRegion];
_patientListView = CommonServiceLocator.ServiceLocator.Current.GetInstance();
_paientListRegion.Add(_patientListView);
_medicineListRegion = _regionManager.Regions[RegionNames.MedicineMainContentRegion];
}
///
/// 失效medicineMainContent檢視
///
void ExecuteDeactiveMedicineListCommand()
{
_medicineListRegion.Deactivate(_medicineMainContentView);
}
///
/// 啟用medicineMainContent檢視
///
void ExecuteActiveMedicineListCommand()
{
_medicineListRegion.Activate(_medicineMainContentView);
}
///
/// 失效patientList檢視
///
void ExecuteDeactivePaientListCommand()
{
_paientListRegion.Deactivate(_patientListView);
}
///
/// 啟用patientList檢視
///
void ExecuteActivePaientListCommand()
{
_paientListRegion.Activate(_patientListView);
}
///
/// 載入MedicineModule
///
void ExecuteLoadMedicineModuleCommand()
{
_moduleManager.LoadModule("MedicineModule");
_medicineMainContentView = (MedicineMainContent)_medicineListRegion.Views
.Where(t => t.GetType() == typeof(MedicineMainContent)).FirstOrDefault();
this.IsCanExcute = true;
}
```
效果如下:
![](https://img2020.cnblogs.com/blog/1294271/202003/1294271-20200331150701465-763264378.gif)
### 監控檢視啟用狀態
Prism其中還支援監控檢視的啟用狀態,是通過在View中繼承IActiveAware來實現的,我們以監控其中MedicineMainContent檢視的啟用狀態為例子:
MedicineMainContentViewModel.cs:
```c#
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());
}
}
}
```
![](https://img2020.cnblogs.com/blog/1294271/202003/1294271-20200331150713576-1911934096.gif)
### Add和Remove
上述例子用的是ContentControl,我們再用一個ItemsControl的例子,程式碼如下:
MainWindow.xaml:
```xaml
```
MainWindow.cs:
```c#
public MainWindow()
{
InitializeComponent();
var regionManager= ServiceLocator.Current.GetInstance();
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:
```xaml
```
ShowSearchPatientViewModel.cs:
```c#
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;
}
///
/// 啟用檢視
///
private void ActiveShowSearchPatient()
{
if (!_region.ActiveViews.Contains(_showSearchPatientView))
{
_region.Add(_showSearchPatientView);
}
}
///
/// 失效檢視
///
private async void DeactiveShowSearchPaitent()
{
_region.Remove(_showSearchPatientView);
await Task.Delay(2000);
IsShow = true;
}
```
效果如下:
![](https://img2020.cnblogs.com/blog/1294271/202003/1294271-20200331150729733-326316595.gif)
這裡的WindowCommands 的繼承鏈為:WindowCommands <-- ToolBar <-- HeaderedItemsControl <--ItemsControl,因此由於Prism預設的介面卡有ItemsControlRegionAdapter,因此其子類也繼承了其行為
這裡重點歸納一下:
- 當進行模組化時,載入完模組才會去注入檢視到區域(可參考MedicineModule檢視載入順序)
- ContentControl控制元件由於Content只能顯示一個,在其區域中可以通過Activate和Deactivate方法來控制顯示哪個檢視,其行為是由ContentControlRegionAdapter介面卡控制
- ItemsControl控制元件及其子控制元件由於顯示一個集合檢視,預設全部集合檢視是啟用的,這時候不能通過Activate和Deactivate方式來控制(會報錯),通過Add和Remove來控制要顯示哪些檢視,其行為是由ItemsControlRegionAdapter介面卡控制
- 這裡沒講到Selector控制元件,因為也是繼承自ItemsControl,因此其SelectorRegionAdapter介面卡和ItemsControlRegionAdapter介面卡異曲同工
- 可以通過繼承IActiveAware介面來監控檢視啟用狀態
## 四.自定義區域介面卡
我們在介紹整個區域管理器模型圖中說過,Prism有三個預設的區域介面卡:ItemsControlRegionAdapter,ContentControlRegionAdapter,SelectorRegionAdapter,且支援自定義區域介面卡,現在我們來自定義一下介面卡
### 1.建立自定義介面卡
新建類UniformGridRegionAdapter.cs:
```c#
public class UniformGridRegionAdapter : RegionAdapterBase
{
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();
}
}
```
### 2.註冊對映
App.cs:
```c#
protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings)
{
base.ConfigureRegionAdapterMappings(regionAdapterMappings);
//為UniformGrid控制元件註冊介面卡對映
regionAdapterMappings.RegisterMapping(typeof(UniformGrid),Container.Resolve());
}
```
### 3.為控制元件建立區域
MainWindow.xaml:
```xaml
```
### 4.為區域注入檢視
這裡用的是ViewInjection方式:
MainWindowViewModel.cs
```c#
void ExecuteLoadingCommand()
{
_regionManager = CommonServiceLocator.ServiceLocator.Current.GetInstance();
var uniformContentRegion = _regionManager.Regions["UniformContentRegion"];
var regionAdapterView1 = CommonServiceLocator.ServiceLocator.Current.GetInstance();
uniformContentRegion.Add(regionAdapterView1);
var regionAdapterView2 = CommonServiceLocator.ServiceLocator.Current.GetInstance();
uniformContentRegion.Add(regionAdapterView2);
}
```
效果如圖:
![](https://img2020.cnblogs.com/blog/1294271/202003/1294271-20200331150744622-1824940697.png)
我們可以看到我們為UniformGrid建立區域介面卡,並且註冊後,也能夠為UniformGrid控制元件建立區域,並且注入檢視顯示,如果沒有該區域介面卡,則是會報錯,下一篇我們將會講解基於區域Region的prism導航系統。
## 五.原始碼
最後,附上整個demo的原始碼:[PrismDemo原始碼]( https://github.com/ZhengDaoWang/PrismMetro