Prism之Region(1)

Prism可以帮助我们开发模块化程序,将程序分割成一个个独立的Module,分别进行开发。然后在程序运行的时候,将各个Module组合到一起,为程序提供各种各样的功能。通常来说,Module是一些视图和功能的集合,那么就需要一种办法来将这些视图以某种形式,在特定的时间展现出来。Prism通过Shell + Region来组织视图的布局,完成视图间的转换等。

 

image

 

如上图所示,Shell相当于ASP.NET中的母版页,它定义了页面的布局、主题等。其中的导航区和内容区是预留出来的需要进行填充内容的部分,也就是Region,起到占位符的作用,程序会在运行时动态地向Region中填充内容。

那么如何将一个区域定义为Region呢?

首先在引入Prism的命名空间

xmlns:prism="http://www.codeplex.com/prism"   如果IDE无法找到这个命名空间的话,需要先注册Prism

然后在需要定义为Region的控件上加上Attached Property。

<ContentControl prism:RegionManager.RegionName="MainRegion" />

并不是所有的控件都可以作为Region的,需要为需要定义为Region的控件添加RegionAdapter。RegionAdapter的作用是为特定的控件创建相应的Region,并将控件与Region进行绑定,然后为Region添加一些行为。一个RegionAdapter需要实现IRegionAdapter接口,如果你需要自定义一个RegionAdapter,可以通过继承RegionAdapterBase类来省去一些工作。Prism为Silverlight提供了几个RegionAdapter:

  • ContentControlRegionAdapter: 创建一个SingleActiveRegion并将其与ContentControl绑定
  • ItemsControlRegionAdapter: 创建一个AllActiveRegion并将其与ItemsControl绑定
  • SelectorRegionAdapter: 创建一个Region并将其与Selector绑定
  • TabControlRegionAdapter: 创建一个Region并将其与TabControl绑定

从图中可以看到,导航区对应的NavigationRegion中四个视图都是亮着的,而内容区对应的ContentRegion中四个视图只有一个是亮着的(橘黄色代表显示在页面中)。ItemsControl本来就是由许多个Item组成的,因此ItemsControlRegionAdapter会创建AllActiveRegion,这种类型的Region中所有Active的视图都会显示在ItemsControl中;而ContentControl只能容纳一个Content,所以ContentControlRegionAdapter创建了一个SingleActiveRegion,其中的视图只有一个是处于Active状态的,会显示在ContentControl中,其它的都是不可见的,需要将它们激活(Active),才能使其显示。

通常我们并不直接和Region打交道,而是通过RegionManager,它实现了IRegionManager接口。IRegionManager接口包含一个只读属性Regions,是Region的集合,还有一个CreateRegionManager方法。Prism通过RegionManagerExtensions类使用扩展方法为IRegionManager添加了更多的功能。

  • AddToRegion: 将一个视图添加到一个Region中。
  • RegisterViewWithRegion: 将一个视图和一个Region进行关联。当Region显示的时候,关联的视图才会显示,也就是说,在这个Region显示之前,关联的视图是不会被创建的。
  • RequestNavigate: 进行页面切换,将指定的Region中显示的视图切换为指定的视图。

本文开头说过,需要在运行时将分散在各个Module的视图显示在页面特定的位置上。那么首先就需要定义页面显示的地方,即Region。然后就是要定义创建视图的时机和方式。在Prism中有两种方式来定义视图与Region之间的映射关系——View Discovery和View Injection。

View Discovery是以声明式的方式来建立Region和视图之间的关系。如上图中的导航区,需要在导航区显示的时候就将各个导航视图填充到其中。而内容区中也需要一个默认显示的内容视图。因此也可以这样理解View Discovery,就是指定一个Region的默认视图。我们可以使用IRegionManager.RegisterViewWithRegion方法来声明某个Region默认应该显示哪个视图。注意这里是Register,是注册,也就是说不会马上创建该视图。当Region显示在页面中的时候,它会去寻找与自己相关联的视图,并对其进行初始化。

1-30-2011 5-03-27 PM

1-30-2011 5-04-22 PM

这样做的好处是我们不必关注在什么时候创建视图,一切都会自动完成。缺点就是默认视图是确定的,当需要进行视图转换的时候,这种方式就行不通了。这时候就需要View Injection。

View Injection可以让我们对于Region中显示的视图有更精确的控制。通常可以通过调用IRegionManager.AddToRegion方法或者是IRegionManager.Regions[“RegionName”].Add方法来向一个Region中添加一个视图的实例。对于SingleActiveRegion(ContentControlRegionAdapter会创建这种类型的Region),可以通过IRegion.Activate方法将一个已经添加到Region中的视图显示出来。当然也可以通过IRegion.Deactivate方法来将视图状态置为非激活或者干脆调用IRegion.Remove方法将视图移除。可以看到,因为要添加的是视图的实例,所以需要仔细地设计在什么时候使用View Injection,以免造成不必要的开销。

在Prism 4.0中新添加了一些导航API,这套API大大地简化了View Injection的流程,它使用URI来进行Region中视图的导航,然后会根据URI来创建视图,并将其添加到Region中,然后激活该视图。导航API的出现不只是为了简化View Injection的过程,它还提供了前进、后退的功能,并且对MVVM模式下的导航有良好的支持,还能够在进行导航的时候传递参数等等。所以推荐的方式是使用新的导航API,也就是使用IRegionManager.RequestNavigate方法。

如果一个页面相对来说不大变化,如导航区,在程序初始化的过程完成后就不会轻易地变动,这时候就较适合于使用RegisterViewWithRegion方法,通常可以在Module的Initialize方法中完成这个过程。

?
1
2
3
4
5
6
7
8
public void Initialize()
{
     logger.Log( "初始化Navigation模块" , Category.Debug, Priority.Low);
     _regionManager.RegisterViewWithRegion(RegionNames.NavRegion, typeof (NavigationItem));
     _regionManager.RegisterViewWithRegion(RegionNames.MainRegion, // 两种方式都可以
                                             () => _container.Resolve<NavigationContainer>() );
     _regionManager.RegisterViewWithRegion(RegionNames.NavDemoActionRegion, typeof (ActionController));
}

如果一个区域需要频繁地切换页面的话,如主内容区,可以使用View Injection的方式。

?
1
2
3
4
IRegionManager regionManager = ...;
IRegion mainRegion = regionManager.Regions[ "MainRegion" ];
InboxView view = this .container.Resolve<InboxView>();
mainRegion.Add(view);

可以看到,这时候已经生成了视图的实例。之前提到过,一个Region可以包含多个视图,这些视图会处于不同的状态,对于ItemsControl类型的Region来说,里面会显示很多个Item,所以添加进去就可以了;但是对于ContentControl这种Region,同一时刻只能显示一个视图,所以在添加进去之后还需要有一个Activate的过程。

使用URI来进行导航只需要提供需要切换的视图的名称就可以,并不需要了解视图的类型,从而达到解耦的目的,并且可以通过URI来进行参数传递。

?
1
2
3
public void Initialize()
     //  因为Prism无法确定每个视图都是什么类型,所以就使用了Object,因此在根据ViewName获取实例时,会使用IServiceLocator.GetInstance<Object>(ViewName)
?
1
2
3
4
     _container.RegisterType< object , ViewA>(ViewNames.ViewA);
     _container.RegisterType< object , ViewB>(ViewNames.ViewB);
     _container.RegisterType< object , ViewC>(ViewNames.ViewC);
}

首先注册一下视图的类型,其实就是将视图的名称与视图类型进行一下关联。在导航的时候调用RequestNavigate方法就可以了。

?
1
2
3
4
5
6
void ToSpecifiedView( string viewName)
{         
     Uri uri = new Uri(viewName, UriKind.Relative);
     _regionManager.RequestNavigate(RegionNames.NavDemoShowRegion, uri);
     logger.Log( "跳转到视图 [" + viewName + "]" , Category.Info, Priority.Low);
}

Prism提供了UriQuery类来帮助我们在导航的时候传递参数。

?
1
2
3
4
5
6
7
8
9
10
void ToSpecifiedView( string viewName)
{
     UriQuery query = new UriQuery();
     if (viewName == ViewNames.ViewA)
     {
         query.Add( "Time" , DateTime.Now.ToShortTimeString());
     }
     Uri uri = new Uri(viewName + query.ToString(), UriKind.Relative);
     _regionManager.RequestNavigate(RegionNames.NavDemoShowRegion, uri, CallbackHandler);   //  回调方法可加可不加
}

上面的代码判断当跳转到ViewA时,传递一个叫做Time的参数。那么怎样在视图中获取传递的参数呢?这里就要提一下INavigationAware接口了。这个接口使视图或者其对应的ViewModel也可以参与到页面导航的过程中来。所以这个接口既可以由视图来实现,也可以由视图的DataContext——通常指的就是ViewModel,来实现。

?
1
2
3
4
5
6
public interface INavigationAware
{
     bool IsNavigationTarget(NavigationContext navigationContext);
     void OnNavigatedTo(NavigationContext navigationContext);
     void OnNavigatedFrom(NavigationContext navigationContext);
}

当从本页面转到其它页面的时候,会调用OnNavigatedFrom方法,navigationContext会包含目标页面的URI。

当从其它页面导航至本页面的时候,首先会调用IsNavigationTarget,IsNavigationTarget返回一个bool值,简单地说这个方法的作用就是告诉Prism,是重复使用这个视图的实例还是再创建一个。然后调用OnNavigatedTo方法。在导航到本页面的时候,就可以从navigationContext中取出传递过来的参数。

image

使用导航API的另一个优点就是可以进行页面的前进和后退,一切由Prism完成。这个功能是由IRegionNavigationJournal接口提供的。

?
1
2
3
4
5
6
7
8
9
10
11
public interface IRegionNavigationJournal
{
     bool CanGoBack { get ; }
     bool CanGoForward { get ; }
     IRegionNavigationJournalEntry CurrentEntry { get ; }
     INavigateAsync NavigationTarget { get ; set ; }
     void Clear();
     void GoBack();
     void GoForward();
     void RecordNavigation(IRegionNavigationJournalEntry entry);
}

其中CanGoBack和CanGoForward属性表示当前是否可以后退或前进。如果可以的话,可以使用GoBack和GoForward方法进行前进和后退。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ActionControllerViewModel : NotificationObject
{
     private IRegion _demoShowRegion;
     public bool CanGoBack
     {
         get
         {
             return _demoShowRegion.NavigationService.Journal.CanGoBack;
         }
     }
 
     public bool CanGoForward
     {
         get
         {
             return _demoShowRegion.NavigationService.Journal.CanGoForward;
         }
     }
 
     void ToPrevious()
     {
         _demoShowRegion.NavigationService.Journal.GoBack();
         ResetNavigationButtonState();
     }
 
     void ToNext()
     {
         _demoShowRegion.NavigationService.Journal.GoForward();
         ResetNavigationButtonState();
     }
 
     void ResetNavigationButtonState()
     {
         RaisePropertyChanged(() => this .CanGoBack);
         RaisePropertyChanged(() => this .CanGoForward);
     }
}

image

 

导航API还可以控制视图的生命周期,在页面跳转时进行确认拦截(Confirming or Cancelling Navigation)以及其它功能,可以参考 Developer’s Guide to Microsoft Prism

### Prism Framework Region Implementation and Usage In the context of WPF or Xamarin.Forms applications using the Prism library, regions provide a way to manage views within an application dynamically. Regions allow developers to define areas in a user interface where different views can be inserted at runtime. #### Defining Regions in XAML Regions are typically defined as controls inside a view that will host other views. In WPF, this is often done by adding a `ContentControl` with a specific region name: ```xml <Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <!-- Define a named region --> <ContentControl prism:RegionManager.RegionName="MainRegion"/> </Grid> </Window> ``` For Xamarin.Forms, similar functionality can be achieved through custom renderers or behaviors but primarily involves setting up a placeholder for dynamic content insertion[^1]. #### Registering Views with Regions To associate views with these regions programmatically during initialization, one uses dependency injection containers provided by Prism. This registration ensures that when navigation commands target certain regions, appropriate views get instantiated and displayed there. ```csharp protected override void ConfigureViewModelLocator() { base.ConfigureViewModelLocator(); // Register main shell/view containing regions. Container.RegisterType<object, MainShell>(typeof(MainShell).FullName); } // Within module initializations... public class MyModule : IModule { public void OnInitialized(IContainerProvider containerProvider) { var regionManager = containerProvider.Resolve<IRegionManager>(); // Add ViewA into 'MainRegion' regionManager.RequestNavigate("MainRegion", "ViewA"); } } ``` This approach facilitates modular development practices allowing teams to work on separate modules independently while ensuring seamless integration via well-defined interfaces like regions. #### Managing Multiple Views per Region It's also possible to add multiple views to a single region which might require configuring how they should behave (e.g., stacking order): ```csharp regionManager.Regions["AnotherRegion"].Add(new AnotherView(), "UniqueKey"); regionManager.Regions["AnotherRegion"].Activate(anotherViewInstance); // Activates previously added view. ``` Such flexibility supports complex UI requirements without tightly coupling components together directly within code-behind files. --related questions-- 1. How does Prism handle view lifecycle management? 2. What benefits do regions offer over direct child element manipulation in XAML? 3. Can you explain strategies for lazy loading views associated with regions? 4. Are there any performance considerations when working extensively with many regions?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值