1. 程式人生 > >.NET Core 3 WPF MVVM框架 Prism系列之區域管理器

.NET Core 3 WPF MVVM框架 Prism系列之區域管理器

本文將介紹如何在.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 ``` 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