本文將介紹如何在.NET Core3環境下使用MVVM框架Prism的使用區域管理器對於View的管理git
咱們在以前的Prism系列構建了一個標準式Prism項目,這篇文章將會講解以前項目中用到的利用區域管理器更好的對咱們的View進行管理,一樣的咱們來看看官方給出的模型圖:github
如今咱們能夠知道的是,大體一個區域管理器RegionMannager對一個控件建立區域的要點:express
其實後來我去看了下官方的介紹和源碼,默認RegionAdapter是有三個,且還支持自定義RegionAdapter,所以在官方的模型圖之間我作了點補充:編程
咱們先來看看咱們以前項目的區域的劃分,以及如何建立區域而且把View注入到區域中:c#
咱們把整個主窗體劃分了四個區域:app
在Prism中,咱們有兩種方式去實現區域建立和視圖注入:框架
咱們截取其中PatientListRegion的建立和視圖注入的代碼(更仔細的能夠去觀看demo源碼):async
MainWindow.xaml:ide
<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) { } }
咱們在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:
首先咱們須要控制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; }
效果以下:
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()); } } }
上述例子用的是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,所以其子類也繼承了其行爲
這裏重點概括一下:
咱們在介紹整個區域管理器模型圖中說過,Prism有三個默認的區域適配器:ItemsControlRegionAdapter,ContentControlRegionAdapter,SelectorRegionAdapter,且支持自定義區域適配器,如今咱們來自定義一下適配器
新建類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(); } }
App.cs:
protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings) { base.ConfigureRegionAdapterMappings(regionAdapterMappings); //爲UniformGrid控件註冊適配器映射 regionAdapterMappings.RegisterMapping(typeof(UniformGrid),Container.Resolve<UniformGridRegionAdapter>()); }
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>
這裏用的是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導航系統。
最後,附上整個demo的源代碼:PrismDemo源碼