本文將介紹如何在.NET Core3環境下使用MVVM框架Prism的使用區域管理器對於View的管理
一.區域管理器#
我們在之前的Prism系列構建了一個標準式Prism項目,這篇文章將會講解之前項目中用到的利用區域管理器更好的對我們的View進行管理,同樣的我們來看看官方給出的模型圖:
現在我們可以知道的是,大致一個區域管理器RegionMannager對一個控件創建區域的要點:
- 創建Region的控件必須包含一個RegionAdapter適配器
- region是依賴在具有RegionAdapter控件身上的
其實後來我去看了下官方的介紹和源碼,默認RegionAdapter是有三個,且還支持自定義RegionAdapter,因此在官方的模型圖之間我做了點補充:
二.區域創建與視圖的注入#
我們先來看看我們之前項目的區域的劃分,以及如何創建區域並且把View注入到區域中:
我們把整個主窗體劃分了四個區域:
- ShowSearchPatientRegion:注入了ShowSearchPatient視圖
- PatientListRegion:注入了PatientList視圖
- FlyoutRegion:注入了PatientDetail和SearchMedicine視圖
- ShowSearchPatientRegion:注入了ShowSearchPatient視圖
在Prism中,我們有兩種方式去實現區域創建和視圖注入:
- ViewDiscovery
- ViewInjection
1.ViewDiscovery#
我們截取其中PatientListRegion的創建和視圖注入的代碼(更仔細的可以去觀看demo源碼):
MainWindow.xaml:
<code>Copy /<code>
這裡相當於在後臺MainWindow.cs:
<code>CopyRegionManager.SetRegionName(ContentControl, "PatientListRegion"); /<code>
PatientModule.cs:
<code>Copy 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) { } } /<code>
2.ViewInjection#
我們在MainWindow窗體的Loaded事件中使用ViewInjection方式注入視圖PatientList
MainWindow.xaml:
<code>Copy /i:EventTrigger> /<code>
MainWindowViewModel.cs:
<code>Copy 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); } /<code>
我們可以明顯的感覺到兩種方式的不同,ViewDiscovery方式是自動地實例化視圖並且加載出來,而ViewInjection方式則是可以手動控制注入視圖和加載視圖的時機(上述例子是通過Loaded事件),官方對於兩者的推薦使用場景如下:
ViewDiscovery:
- 需要或要求自動加載視圖
- 視圖的單個實例將加載到該區域中
ViewInjection:
- 需要顯式或編程控制何時創建和顯示視圖,或者您需要從區域中刪除視圖
- 需要在區域中顯示相同視圖的多個實例,其中每個視圖實例都綁定到不同的數據
- 需要控制添加視圖的區域的哪個實例
- 應用程序使用導航API(後面會講到)
三.激活與失效視圖#
Activate和Deactivate#
首先我們需要控制PatientList和MedicineMainContent兩個視圖的激活情況,上代碼:
MainWindow.xaml:
<code>Copy /<code>
MainWindowViewModel.cs:
<code>Copy 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; } /<code>
效果如下:
監控視圖激活狀態#
Prism其中還支持監控視圖的激活狀態,是通過在View中繼承IActiveAware來實現的,我們以監控其中MedicineMainContent視圖的激活狀態為例子:
MedicineMainContentViewModel.cs:
<code>Copy 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()); } } } /<code>
Add和Remove#
上述例子用的是ContentControl,我們再用一個ItemsControl的例子,代碼如下:
MainWindow.xaml:
<code>Copy /<code>
MainWindow.cs:
<code>Copy 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); } /<code>
ShowSearchPatient.xaml:
<code>Copy /<code>
ShowSearchPatientViewModel.cs:
<code>Copy 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; } /<code>
效果如下:
這裡的WindowCommands 的繼承鏈為:WindowCommands
這裡重點歸納一下:
- 當進行模塊化時,加載完模塊才會去注入視圖到區域(可參考MedicineModule視圖加載順序)
- ContentControl控件由於Content只能顯示一個,在其區域中可以通過Activate和Deactivate方法來控制顯示哪個視圖,其行為是由ContentControlRegionAdapter適配器控制
- ItemsControl控件及其子控件由於顯示一個集合視圖,默認全部集合視圖是激活的,這時候不能通過Activate和Deactivate方式來控制(會報錯),通過Add和Remove來控制要顯示哪些視圖,其行為是由ItemsControlRegionAdapter適配器控制
- 這裡沒講到Selector控件,因為也是繼承自ItemsControl,因此其SelectorRegionAdapter適配器和ItemsControlRegionAdapter適配器異曲同工
- 可以通過繼承IActiveAware接口來監控視圖激活狀態
四.自定義區域適配器#
我們在介紹整個區域管理器模型圖中說過,Prism有三個默認的區域適配器:ItemsControlRegionAdapter,ContentControlRegionAdapter,SelectorRegionAdapter,且支持自定義區域適配器,現在我們來自定義一下適配器
1.創建自定義適配器#
新建類UniformGridRegionAdapter.cs:
<code>Copypublic 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(); } } /<code>
2.註冊映射#
App.cs:
<code>Copyprotected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings) { base.ConfigureRegionAdapterMappings(regionAdapterMappings); //為UniformGrid控件註冊適配器映射 regionAdapterMappings.RegisterMapping(typeof(UniformGrid),Container.Resolve()); } /<code>
3.為控件創建區域#
MainWindow.xaml:
<code>Copy /<code>
4.為區域注入視圖#
這裡用的是ViewInjection方式:
MainWindowViewModel.cs
<code>Copy 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); } /<code>
效果如圖:
我們可以看到我們為UniformGrid創建區域適配器,並且註冊後,也能夠為UniformGrid控件創建區域,並且注入視圖顯示,如果沒有該區域適配器,則是會報錯,下一篇我們將會講解基於區域Region的prism導航系統。
五.源碼#
最後,附上整個demo的源代碼:PrismDemo源碼
作者: RyzenAdorer
出處:https://www.cnblogs.com/ryzen/p/12605347.html