Region可以帮助我们开发模块化程序,将程序分割成一个个独立的Module,分别进行开发。然后在程序运行的时候,将各个Module组合到一起,为程序提供各种各样的功能。通常来说,Module是一些视图和功能的集合,那么就需要一种办法来将这些视图以某种形式,在特定的时间展示出来。Prism通过Shell+Region来组织视图的布局,完成视图间的转换等。
如上图所示,Shell相当于ASP.NET中的母版页,它定义了页面的布局、主题等。其中的导航区和内容区是预留出来的需要进行填充内容的部分,也就是Region,起到占位符的作用,程序会在运行时动态地向Region中填充内容。
那么如何将一个区域定义为Region呢?
1. 首先要引入Prism命名空间
xmlns:prism="http://www.codeplex.com/prism" 如果IDE无法找到这个命名空间的话,需要先注册Prism。
2. 然后再需要定义为Region的控件上加上Attached Property。
<ContentCotrol prism:RegionManager.RegionName = "MainRegion"/>
并不是所哟的控件都可以作为Region的,需要为需要定义为Region的控件添加RegionAdapter。RegionAdapter的作用是为特定的控件创建相应的Region,并将控件与Region进行绑定,然后为Region添加一些行为。一个RegionAdapter需要实现IRegionAdapte接口,如果你需要自定义一个RegionAdapter,可以通过继承RegionAdapterBase类来省去一些工作。Prism为Silverlight提供了几个RegionAdapter:
ContentControlRegionAdapter:创建一个SingleActiveRegion并将其与ContentControl绑定。
ItemsControlRegionAdapter: 创建一个AllActiveRegion并将其与ItemsControl绑定。
SelectorRegionAdapter: 创建一个Region并将其与Selector绑定。
TabControlRegionAdapter: 创建一个Region并将其与TabControl绑定。
从图中可以看到,导航区对应的NavigationRegion中四个视图都是亮着的,而内容区对应的ContentRegion中四个视图只有一个是亮着的(橙色代表显示在页面中)。ItemsControl本来就是由许多个Items组成的,因此ItemsControlRegionAdapter会创建AllActiveRegion,这种类型的Region中所有Active的视图都会显示在ItemsControl中;而ContentControl只能容纳一个Content,所以ContentControlRegionAdapter创建了一个SingleActiveRegion,其中的视图只有一个处于Active状态的,会显示在ContentControl中,其他的都是不可见的,需要将他们激活(Active),才能使其显示。
通常我们并不直接和Region打交道,而是通过RegionManager,它实现了IRegionManager接口。IRegionManager接口包含一个只读属性Regions,是Region的集合,还有一个CreatRegionManager方法。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显示在页面中的时候,它回去寻找与自己关联的视图,并对其进行初始化。
这样做的好处是我们不必关心在什么时候创建视图,一切都会自动完成。缺点就是默认视图是确定的,当需要进行视图转换的时候,这种方式就行不通了。这时候就需要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方法。
如果一个页面相对来说不大变化,如导航区,在程序初始化的过程完成后就不会轻易变动,这时候就较适合私用RegisterViewRegion方法,通常可以在Module的Initialize方法中完成这个过程。
下面通过一个Demo来学习Region:
1. 新建一个Silverlight Application——“PrismRegionDemo”,然后再创建3个Silverlight类库工程:
2. 首先在Infrastructure——基础结构项目中来实现一些需要使用的Prism的接口。首先在该项目中引用Microsoft.Practices.Prism程序集,然后新建一个类CallbackLogger.cs该类实现了Prism内置的ILoggerFacade接口,在Prism提供的QuickStart项目里的Modularity中,有一个CallBackLogger,这里我们直接拿过来使用。该类是为了在UI上显示LOG信息使用。然后再PrismRegionShell中放一个TextBox,将log的内容显示在这个TextBox中。
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Practices.Prism.Logging;
using System.Collections.Generic;
namespace Infrastrcture
{
//A logger that holds on to log entries until a callback delegate is set, then plays back log entries and sends new log entries.
public class CallBackLogger:ILoggerFacade
{
private Queue<Tuple<string, Category, Priority>> saveLogs = new Queue<Tuple<string, Category, Priority>>();
private Action<string, Category, Priority> callback;
//Gets or Sets the callback to receive logs.
public Action<string, Category, Priority> CallBack
{
get
{
return this.callback;
}
set
{
this.callback = value;
}
}
/// <summary>
/// Write a new log entry with the specified category and priority.
/// </summary>
/// <param name="message">Message body to log.</param>
/// <param name="category">Category of the entry.</param>
/// <param name="priority">The priority of the entry.</param>
public void Log(string message, Category category, Priority priority)
{
if (this.CallBack != null)
{
this.CallBack(message, category, priority);
}
else
{
saveLogs.Enqueue(new Tuple<string, Category, Priority>(message, category, priority));
}
}
//Replay the saved logs if the Callback has been set.
public void ReplaySaveLogs()
{
if (this.CallBack != null)
{
while (this.saveLogs.Count > 0)
{
var log = this.saveLogs.Dequeue();
this.CallBack(log.Item1, log.Item2, log.Item3);
}
}
}
}
}
3. 在Infrastructure里创建一个类RegionNames,用来存储RegionName的常量字符串。
-
public class RegionNames
-
{
-
public const string NavRegion = "NavRegion";
-
public const string MainRegion = "MainRegion";
-
public const string NavDemoShowRegion = "NavDemoShowRegion";
-
public const string NavDemoActionRegion = "NavDemoActionRegion";
-
public const string TabShowRegion = "TabShowRegion";
-
}
4. 在Infrastructure里新建一个类ViewNames,用来存储ViewNames常量字符串。
-
public class ViewNames
-
{
-
public const string NavigationContainer = "NavigationContainer";
-
public const string EmptyPage = "EmptyPage";
-
public const string ViewA = "ViewA";
-
public const string ViewB = "ViewB";
-
public const string ViewC = "ViewC";
-
}
5. 删除PrismRegionDemo项目中自动生成的MainPage.xaml,创建一个新的UserControl,叫做PrismRegionShell。添加对Prism程序集的引用(Microsoft.Practices.Prim;Microsoft.Pratices.Prism.UnityExtensions;Microsoft.Practices.Unity.Silverlight),并且还要添加对页面导航的引用(System.Windows.Controls.Navigatio),以及Infrastructure项目的引用,页面代码如下:
-
<UserControl x:Class="PrismRegionDemo.PrismRegionShell"
-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-
mc:Ignorable="d"
-
xmlns:prism="http://www.codeplex.com/prism"
-
d:DesignHeight="600" d:DesignWidth="800">
-
<UserControl.Resources>
-
<Style TargetType="Border" x:Key="BorderStyle">
-
<Setter Property="Background" Value="RosyBrown"/>
-
<Setter Property="Opacity" Value="0.8"/>
-
<Setter Property="Margin" Value="15"/>
-
<Setter Property="CornerRadius" Value="5"/>
-
<Setter Property="Effect">
-
<Setter.Value>
-
<DropShadowEffect ShadowDepth="15" Color="Black" Direction="320" Opacity="0.5"/>
-
</Setter.Value>
-
</Setter>
-
</Style>
-
</UserControl.Resources>
-
<Grid ShowGridLines="True">
-
<Grid.Background>
-
<ImageBrush ImageSource="/PrismRegionDemo;component/Images/20070825000100735.jpg" Stretch="UniformToFill"/>
-
</Grid.Background>
-
<Grid.RowDefinitions>
-
<RowDefinition MinHeight="400" Height="0.6*"/>
-
<RowDefinition MinHeight="200" Height="0.4*"/>
-
</Grid.RowDefinitions>
-
<Grid.ColumnDefinitions>
-
<ColumnDefinition MinWidth="280" Width="0.4*"/>
-
<ColumnDefinition Width="0.6*"/>
-
</Grid.ColumnDefinitions>
-
<Border Style="{StaticResource BorderStyle}" Width="250" HorizontalAlignment="Right">
-
<ItemsControl prism:RegionManager.RegionName="NavRegion"/>
-
</Border>
-
<Border Style="{StaticResource BorderStyle}" HorizontalAlignment="Left" Width="550" Grid.Column="1">
-
<ContentControl VerticalAlignment="Center" HorizontalAlignment="Center" prism:RegionManager.RegionName="MainRegion" Background="White"/>
-
</Border>
-
<Border Style="{StaticResource BorderStyle}" HorizontalAlignment="Left" Width="550" Grid.Row="1" Grid.Column="1">
-
<ContentControl Padding="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
-
<TextBox x:Name="LogContainer" IsReadOnly="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" BorderThickness="0" Background="{x:Null}"/>
-
</ContentControl>
-
</Border>
-
</Grid>
-
</UserControl>
PrismRegionShell中放一个TextBox,将log的内容显示在这个TextBox中。值得一提的是,为了让输出新log的直接显示出来,需要将TextBox的滚动条滚动到最下面。PrismRegionShell.xaml.cs如下:
-
using System;
-
using System.Collections.Generic;
-
using System.Linq;
-
using System.Net;
-
using System.Windows;
-
using System.Windows.Controls;
-
using System.Windows.Documents;
-
using System.Windows.Input;
-
using System.Windows.Media;
-
using System.Windows.Media.Animation;
-
using System.Windows.Shapes;
-
using Infrastrcture;
-
using Microsoft.Practices.Prism.Logging;
-
using System.Globalization;
-
namespace PrismRegionDemo
-
{
-
public partial class PrismRegionShell : UserControl
-
{
-
public PrismRegionShell(CallBackLogger logger)
-
{
-
InitializeComponent();
-
//显示当前Log信息
-
logger.CallBack = Log;
-
}
-
public void Log(string message, Category category, Priority priority)
-
{
-
//[Debug][Low] Setting the RegionManager.
-
this.LogContainer.Text += string.Format(CultureInfo.CurrentUICulture, "[{0}][{1}] {2}\r\n", category, priority, message);
-
//这段代码的作用是让文本框的滚动条滚动到最底部
-
LogContainer.Select(LogContainer.Text.Length, LogContainer.Text.Length);
-
}
-
}
-
}
6. 在EmptyDemoModule中新建一个UserControl——EmptyNavigationItem.xaml,这里只有一个导航按钮"空示例"
-
<UserControl x:Class="EmptyDemoModule.EmptyNavigationItem"
-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-
mc:Ignorable="d"
-
d:DesignHeight="300" d:DesignWidth="400">
-
<Button Content="空示例" FontSize="20" Click="Button_Click"/>
-
</UserControl>
这里也要对该项目添加3个Prim的类库引用(Microsoft.Practices.Prism; Microsoft.Practices.Prism.UnityExtensions;Microsoft.Practces.Unity.Silverlight)以及Infrastructure项目,CodeBehind代码如下:
-
using System;
-
using System.Collections.Generic;
-
using System.Linq;
-
using System.Net;
-
using System.Windows;
-
using System.Windows.Controls;
-
using System.Windows.Documents;
-
using System.Windows.Input;
-
using System.Windows.Media;
-
using System.Windows.Media.Animation;
-
using System.Windows.Shapes;
-
using Microsoft.Practices.Prism.Regions;
-
using Microsoft.Practices.Prism.Logging;
-
using Infrastrcture;
-
namespace EmptyDemoModule
-
{
-
//ViewSortHintAttribute是为了指定哪个View先在Region中显示出来,设置为02的意思是当前排序优先级为02
-
[ViewSortHint("02")]
-
public partial class EmptyNavigationItem : UserControl
-
{
-
IRegionManager _regionManager;
-
ILoggerFacade logger;
-
public EmptyNavigationItem(IRegionManager regionManager,ILoggerFacade logger)
-
{
-
logger.Log("初始化【空示例】按钮", Category.Info, Priority.Low); //显示Log信息
-
InitializeComponent();
-
this._regionManager = regionManager;
-
this.logger = logger;
-
}
-
private void Button_Click(object sender, RoutedEventArgs e)
-
{
-
Uri uri = new Uri(ViewNames.EmptyPage, UriKind.Relative);
-
//导航到MainRegion,把Uri指向的Page显示在PrismRegionShell里命名为MainRegion的Region里
-
_regionManager.RequestNavigate(RegionNames.MainRegion, uri);
-
//显示Log信息
-
logger.Log("导航到Empty示例", Category.Info, Priority.Low);
-
}
-
}
-
}
这里需要注意的是,一个Region里需要同时显示多个视图时,视图的顺序问题。比如ItemsControl,哪个先被注册就哪个显示在上面,但是由于Module的加载速度等原因,这时候就无法判断哪个视图会在上面,现在我们需要指定[导航示例]这个按钮在上,那么Prism为我们提供了ViewSortHintAttribute来解决这个问题。在需要进行排序的视图上添加上相应的Attribute就可以了。
7. 在EmptyDemoModule里新建一个EmptyPage的Usercontrol,用于显示在点击“空示例”后,在MainRegion里显示的Page:
-
<UserControl x:Class="EmptyDemoModule.EmptyPage"
-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-
mc:Ignorable="d"
-
d:DesignHeight="300" d:DesignWidth="400">
-
<Grid x:Name="LayoutRoot" Background="White">
-
<TextBlock Text="Empty" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="100"/>
-
</Grid>
-
</UserControl>
8. 在EmptyDemoModule里新建一个类EmptyModule用于初始化Empty模块,该类实现了Prism的IModule接口。
-
using System;
-
using System.Net;
-
using System.Windows;
-
using System.Windows.Controls;
-
using System.Windows.Documents;
-
using System.Windows.Ink;
-
using System.Windows.Input;
-
using System.Windows.Media;
-
using System.Windows.Media.Animation;
-
using System.Windows.Shapes;
-
using Microsoft.Practices.Prism.Modularity;
-
using Microsoft.Practices.Prism.Regions;
-
using Microsoft.Practices.Unity;
-
using Microsoft.Practices.Prism.Logging;
-
using Infrastrcture;
-
namespace EmptyDemoModule
-
{
-
public class EmptyModule:IModule
-
{
-
IRegionManager _regionManager;
-
IUnityContainer _container;
-
ILoggerFacade logger;
-
public EmptyModule(IUnityContainer container,IRegionManager regionManager,ILoggerFacade logger)
-
{
-
_container = container;
-
_regionManager = regionManager;
-
this.logger = logger;
-
}
-
public void Initialize()
-
{
-
//添加显示Log信息
-
logger.Log("初始化Empty模块", Category.Debug, Priority.Low);
-
//把EmptyNavigationItem这个"按钮"View注册到RegionName为NavRegion的Region上
-
_regionManager.RegisterViewWithRegion(RegionNames.NavRegion, typeof(EmptyNavigationItem));
-
//由于EmptyPage是需要显示在ContentControl里的,同一时刻只能显示一个视图,所以在添加进去之后还需要一个Active的过程,
-
//因为Prism无法确定每个视图是什么类型,所以就使用了Object,因此在根据ViewName获取实例时,
-
//会使用IServiceLocator.GetInstance<Object>(VivewName),来把当前container里显示的View指定为EmptyPage页面
-
_container.RegisterType<object, EmptyPage>(ViewNames.EmptyPage);
-
}
-
}
-
}
9. 下一步我们来在NavigationDemoModule中创建一个ActionController的UserControl,该页面是为了在Container里显示MainRegion中显示导航按钮的。
-
<UserControl x:Class="NavigationDemoModule.ActionController"
-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-
mc:Ignorable="d"
-
d:DesignHeight="300" d:DesignWidth="400">
-
<UserControl.Resources>
-
<Style TargetType="Button">
-
<Setter Property="Width" Value="70"/>
-
<Setter Property="Height" Value="25"/>
-
<Setter Property="Margin" Value="5,0"/>
-
</Style>
-
</UserControl.Resources>
-
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
-
<Button Content="上一页" Command="{Binding Previous}" IsEnabled="{Binding CanGoBack,Mode=TwoWay}"/>
-
<Button Content="View A" Command="{Binding SwitchView}" CommandParameter="ViewA"/>
-
<Button Content="View B" Command="{Binding SwitchView}" CommandParameter="ViewB"/>
-
<Button Content="View C" Command="{Binding SwitchView}" CommandParameter="ViewC"/>
-
<Button Content="下一页" Command="{Binding Next}" IsEnabled="{Binding CanGoForwar,Mode=TwoWay}"/>
-
</StackPanel>
-
</UserControl>
在该项目中引用Prism类库(Microsoft.Practices.Prism; Microsoft.Practices.Prism.UnityExtensions; Microsoft.Practices.Unity.Silverlight),这里采用了Binding绑定到ViewModel的MVVM架构的方式来构筑。
-
using System;
-
using System.Collections.Generic;
-
using System.Linq;
-
using System.Net;
-
using System.Windows;
-
using System.Windows.Controls;
-
using System.Windows.Documents;
-
using System.Windows.Input;
-
using System.Windows.Media;
-
using System.Windows.Media.Animation;
-
using System.Windows.Shapes;
-
using Microsoft.Practices.Prism.Logging;
-
using Microsoft.Practices.Unity;
-
namespace NavigationDemoModule
-
{
-
public partial class ActionController : UserControl
-
{
-
public ActionController(ILoggerFacade logger,IUnityContainer container)
-
{
-
logger.Log("初始化导航示例中的几个按钮的视图", Category.Info, Priority.Low);
-
InitializeComponent();
-
//指定当前Page的DataContext为ActionControllerViewModel这个ViewModel,这是MVVM架构的特性
-
this.Loaded += (s, e) =>
-
{
-
this.DataContext = container.Resolve<ActionControllerViewModel>();
-
};
-
}
-
}
-
}
在该项目中创建一个上面的View对应的ViewModel——ActionControlerViewModel,这个类实现了Prism的NotificationObject,该类实现了MVVM的绑定通知功能
-
using System;
-
using System.Net;
-
using System.Windows;
-
using System.Windows.Controls;
-
using System.Windows.Documents;
-
using System.Windows.Ink;
-
using System.Windows.Input;
-
using System.Windows.Media;
-
using System.Windows.Media.Animation;
-
using System.Windows.Shapes;
-
using Microsoft.Practices.Prism.ViewModel;
-
using Microsoft.Practices.Prism.Regions;
-
using Microsoft.Practices.Unity;
-
using Microsoft.Practices.Prism.Logging;
-
using Infrastrcture;
-
using Microsoft.Practices.Prism.Commands;
-
using Microsoft.Practices.Prism;
-
namespace NavigationDemoModule
-
{
-
public class ActionControllerViewModel:NotificationObject
-
{
-
private IRegionManager _regionManager;
-
private IRegion _demoShowRegion;
-
private IUnityContainer _container;
-
private ILoggerFacade logger;
-
public ActionControllerViewModel(IUnityContainer container, IRegionManager regionManager, ILoggerFacade logger)
-
{
-
_container = container;
-
_regionManager = regionManager;
-
//获取Region为RegionName为NavDemoShowRegion的Region
-
_demoShowRegion = _regionManager.Regions[RegionNames.NavDemoShowRegion];
-
this.logger = logger;
-
//把MVVM中的动作属性方法委托到DelegateCommand()方法里的委托方法去
-
this.Previous = new DelegateCommand(ToPrevious);
-
this.Next = new DelegateCommand(ToNext);
-
this.SwitchView = new DelegateCommand<string>(ToSpecifiedView);
-
}
-
//MVVM用于与页面Binding的动作属性方法
-
public ICommand Previous { get; private set; }
-
public ICommand Next { get; private set; }
-
public ICommand SwitchView { get; private set; }
-
//数据属性 是否可以回退
-
public bool CanGoBack
-
{
-
get
-
{
-
//使用导航API的有点就是可以进行页面的前进和后退,一切后Prism完成。
-
//这一功能是由IRegionNavigationJournal接口提供
-
return _demoShowRegion.NavigationService.Journal.CanGoBack;
-
}
-
}
-
//数据属性 是否可以前进
-
public bool CanGoForward
-
{
-
get
-
{
-
return _demoShowRegion.NavigationService.Journal.CanGoForward;
-
}
-
}
-
//动作属性Previous的实际执行的委托方法ToPrevious
-
void ToPrevious()
-
{
-
_demoShowRegion.NavigationService.Journal.GoBack();
-
ResetNavigationButtonState();
-
}
-
//动作属性Next的实际执行的委托方法ToNext
-
void ToNext()
-
{
-
_demoShowRegion.NavigationService.Journal.GoForward();
-
ResetNavigationButtonState();
-
}
-
//动作属性SwitchView的实际执行的委托方法ToSpecifiedView(string viewName)
-
void ToSpecifiedView(string viewName)
-
{
-
//Prism提供了UriQuery类来帮助我们在导航的时候传递参数
-
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方法用于进行页面切换,将指定的Region中显示的视图切换为指定的视图
-
_regionManager.RequestNavigate(RegionNames.NavDemoShowRegion, uri);
-
logger.Log("跳转到视图[" + viewName + "]", Category.Info, Priority.Low);
-
ResetNavigationButtonState();
-
}
-
private void ResetNavigationButtonState()
-
{
-
RaisePropertyChanged(() => this.CanGoBack);
-
RaisePropertyChanged(() => this.CanGoForward);
-
}
-
}
-
}
在该项目里新建一个UserControl——NavigationContainer,用于做ViewA B C的容器Region使用
-
<UserControl x:Class="NavigationDemoModule.NavigationContainer"
-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-
mc:Ignorable="d"
-
xmlns:prism="http://www.codeplex.com/prism"
-
d:DesignHeight="300" d:DesignWidth="400">
-
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
-
<Border Margin="15" BorderThickness="2" BorderBrush="Red" Width="200" Height="200">
-
<ContentControl prism:RegionManager.RegionName="NavDemoShowRegion"/>
-
</Border>
-
<ContentControl prism:RegionManager.RegionName="NavDemoActionRegion"/>
-
</StackPanel>
-
</UserControl>
后台代码为:
-
namespace NavigationDemoModule
-
{
-
public partial class NavigationContainer : UserControl
-
{
-
ILoggerFacade logger;
-
public NavigationContainer(ILoggerFacade logger)
-
{
-
logger.Log("初始化【导航示例】", Category.Info, Priority.Low);
-
InitializeComponent();
-
this.logger = logger;
-
}
-
}
-
}
在该项目中在新建一个NavigationItem的UserControl,该项用于在导航里显示“导航示例”按钮:
-
<UserControl x:Class="NavigationDemoModule.NavigationItem"
-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-
mc:Ignorable="d"
-
d:DesignHeight="300" d:DesignWidth="400">
-
<Button Content="导航示例" FontSize="20" Click="Button_Click"/>
-
</UserControl>
后台代码为:
-
using System;
-
using System.Collections.Generic;
-
using System.Linq;
-
using System.Net;
-
using System.Windows;
-
using System.Windows.Controls;
-
using System.Windows.Documents;
-
using System.Windows.Input;
-
using System.Windows.Media;
-
using System.Windows.Media.Animation;
-
using System.Windows.Shapes;
-
using Microsoft.Practices.Prism.Regions;
-
using Microsoft.Practices.Prism.Logging;
-
using Infrastrcture;
-
namespace NavigationDemoModule
-
{
-
[ViewSortHint("01")] //注意这个特性标示 用于在ItemControl显示时排序使用
-
public partial class NavigationItem : UserControl
-
{
-
IRegionManager _regionManager;
-
ILoggerFacade logger;
-
public NavigationItem(IRegionManager regionManager,ILoggerFacade logger)
-
{
-
logger.Log("初始化【导航示例】按钮", Category.Info, Priority.Low);
-
InitializeComponent();
-
this._regionManager = regionManager;
-
this.logger = logger;
-
}
-
private void Button_Click(object sender, RoutedEventArgs e)
-
{
-
Uri uri = new Uri(ViewNames.NavigationContainer, UriKind.Relative);
-
//把Region为MainRegion里的视图导航成为uri——也就是NavigationContainer的视图
-
_regionManager.RequestNavigate(RegionNames.MainRegion, uri);
-
logger.Log("导航到Navigation示例", Category.Info, Priority.Low);
-
}
-
}
-
}
接下来,我们来创建在NavigationContainer里要显示的另外3个View
先新建一个ViewA的UserControl:
-
<UserControl x:Class="NavigationDemoModule.ViewA"
-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-
mc:Ignorable="d"
-
d:DesignHeight="300" d:DesignWidth="400">
-
<Grid x:Name="LayoutRoot" Background="White">
-
<TextBlock Text="ViewA" FontSize="23"/>
-
</Grid>
-
</UserControl>
后台代码:
-
using System;
-
using System.Collections.Generic;
-
using System.Linq;
-
using System.Net;
-
using System.Windows;
-
using System.Windows.Controls;
-
using System.Windows.Documents;
-
using System.Windows.Input;
-
using System.Windows.Media;
-
using System.Windows.Media.Animation;
-
using System.Windows.Shapes;
-
using Microsoft.Practices.Unity;
-
using Microsoft.Practices.Prism.Logging;
-
namespace NavigationDemoModule
-
{
-
public partial class ViewA : UserControl
-
{
-
public ViewA(IUnityContainer container,ILoggerFacade logger)
-
{
-
InitializeComponent();
-
logger.Log("创建一个ViewA",Category.Info,Priority.Low);
-
//指定当前View的ViewModel是ViewAViewModel
-
this.DataContext = container.Resolve<ViewAViewModel>();
-
}
-
}
-
}
接下来要创建一个ViewAViewModel的类,用于作为ViewA这个View的ViewModel使用,这里需要提一下的就是INavigationAware接口,这个接口使得视图或者其对应的ViewModel也可以参与到导航的过程中来。所以这个接口既可以由视图来实现,也可以由视图的DataContext——也就是通常所指的ViewModel来实现。
在这个接口中,当从本页面转到其他页面的时候,会调用本页面的OnNavigatedFrom方法,navigationContext中会包含目标页面的URI;
当从其他页面导航到本页面的时候,首先会调用本页面的IsNavigationTarget方法,这个方法返回的是一个bool值,简单的说这个方法就是告诉Prism,是否重复使用这个视图的实例还是在创建一个;然后再调用OnNavigationTo方法。在导航到本页面的时候,就可以从NavigationContext中取出传递的参数。
这里还需要说明一下的是在ActionControllerViewModel中我们在点击ViewA 的时候会把Region指向的页面导航到ViwA上,并且由于使用了UriQuery.Add()方法把key为“Time”的参数的值也传递到了ViewA页面上,我们就可以在ViewA页面中获取到该参数的值,使用的是UriQuery["Key"]来获取。
-
using System;
-
using System.Collections.Generic;
-
using System.Linq;
-
using System.Text;
-
using Microsoft.Practices.Prism.Regions;
-
using Microsoft.Practices.Prism.Logging;
-
using Microsoft.Practices.Prism;
-
namespace NavigationDemoModule
-
{
-
//INavigationAware接口可以使视图或者其对应的ViewModel可以参加到页面导航的过程中来,
-
//这个接口既可以由视图来实现,也可以由视图的DataContext——也就是ViewModel来实现
-
public class ViewAViewModel:INavigationAware
-
{
-
ILoggerFacade logger;
-
public ViewAViewModel(ILoggerFacade logger)
-
{
-
this.logger = logger;
-
}
-
//当从其他页面导航到本页面的时候,首先调用该方法,判断是否要新建一个本页面还是使用原来已有该视图的实例,是否可以重用(true如果之前已经创建了ViewA的对象就使用之前的对象;false不管之前有没有创建ViewA的对象,每次导航到ViewA页面的时候都要创建一个新的ViewA对象)
-
public bool IsNavigationTarget(NavigationContext navigationContext)
-
{
-
return true;
-
}
-
//当从本页面导航到其他页面的时候调用该方法
-
public void OnNavigatedFrom(NavigationContext navigationContext)
-
{
-
}
-
//当从其他页面导航到该页面时候触发该方法
-
public void OnNavigatedTo(NavigationContext navigationContext)
-
{
-
//获取从其他页面导航到该页面时传递的参数集合Parameters
-
UriQuery query = navigationContext.Parameters;
-
//在ActionControllerViewModel里传递过来的UriQuery里有Time这个Key的值,那么在ViewA里就可以获取到该值
-
string time = query["Time"];
-
logger.Log(string.Format("ViewA:现在时间{0}", time), Category.Info, Priority.Medium);
-
}
-
}
-
}
紧接着我们来创建ViewB,也就是点击在ActionController中的“ViewB”按钮的时候,NavigationContainer里所显示的View
-
<UserControl x:Class="NavigationDemoModule.ViewB"
-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-
mc:Ignorable="d"
-
d:DesignHeight="300" d:DesignWidth="400">
-
<TextBlock Text="ViewB" FontSize="23"/>
-
</UserControl>
后台代码实现了INavigationAware接口,也就是可以接受从别的页面导航过来的时候参数,这里就先了实现INavigationAware的两种方式一种是前面我们提到的在ViewModel中继承,另一种就是直接在后台代码里实现该接口。ViewA和ViewB都实现了INavigationAware接口,不同之处在于ViewA是在其对应的ViewModel ViewAViewModel类中实现的,而ViewB是直接在Code Behind中实现的。Prism对MVVM提供了良好的支持,因此既可以选择在视图中实现该接口也可以在对应的ViewModel中实现。
这里还需要注意的是ViewB中的IsNavigationTarget方法返回的是false,而ViewA中则返回了True,可以通过点击三个按钮进行页面跳转,观察Log可以发现,ViewA只创建了一次,而ViewB则每次都要新创建,还有就是在跳转到ViewA的时候传递了参数,可以在OnNavigatedTo方法中取出参数。
ViewB的CodeBehind:
-
using System;
-
using System.Collections.Generic;
-
using System.Linq;
-
using System.Net;
-
using System.Windows;
-
using System.Windows.Controls;
-
using System.Windows.Documents;
-
using System.Windows.Input;
-
using System.Windows.Media;
-
using System.Windows.Media.Animation;
-
using System.Windows.Shapes;
-
using Microsoft.Practices.Prism.Regions;
-
using Microsoft.Practices.Prism.Logging;
-
namespace NavigationDemoModule
-
{
-
public partial class ViewB : UserControl,INavigationAware
-
{
-
public ViewB(ILoggerFacade logger)
-
{
-
InitializeComponent();
-
logger.Log("创建了一个ViewB", Category.Info, Priority.Low);
-
}
-
public bool IsNavigationTarget(NavigationContext navigationContext)
-
{
-
return false;
-
}
-
public void OnNavigatedFrom(NavigationContext navigationContext)
-
{
-
}
-
public void OnNavigatedTo(NavigationContext navigationContext)
-
{
-
}
-
}
-
}
-
using System;
-
using System.Collections.Generic;
-
using System.Linq;
-
using System.Net;
-
using System.Windows;
-
using System.Windows.Controls;
-
using System.Windows.Documents;
-
using System.Windows.Input;
-
using System.Windows.Media;
-
using System.Windows.Media.Animation;
-
using System.Windows.Shapes;
-
using Microsoft.Practices.Prism.Regions;
-
using Microsoft.Practices.Prism.Logging;
-
namespace NavigationDemoModule
-
{
-
public partial class ViewB : UserControl,INavigationAware
-
{
-
public ViewB(ILoggerFacade logger)
-
{
-
InitializeComponent();
-
logger.Log("创建了一个ViewB", Category.Info, Priority.Low);
-
}
-
public bool IsNavigationTarget(NavigationContext navigationContext)
-
{
-
return false;
-
}
-
public void OnNavigatedFrom(NavigationContext navigationContext)
-
{
-
}
-
public void OnNavigatedTo(NavigationContext navigationContext)
-
{
-
}
-
}
-
}
下一步创建ViewC:
-
<UserControl x:Class="NavigationDemoModule.ViewC"
-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-
mc:Ignorable="d"
-
d:DesignHeight="300" d:DesignWidth="400">
-
<Grid x:Name="LayoutRoot" Background="White">
-
<TextBlock Text="ViewC" FontSize="23"/>
-
</Grid>
-
</UserControl>
ViewC的CodeBehind:
-
using System;
-
using System.Collections.Generic;
-
using System.Linq;
-
using System.Net;
-
using System.Windows;
-
using System.Windows.Controls;
-
using System.Windows.Documents;
-
using System.Windows.Input;
-
using System.Windows.Media;
-
using System.Windows.Media.Animation;
-
using System.Windows.Shapes;
-
using Microsoft.Practices.Prism.Logging;
-
namespace NavigationDemoModule
-
{
-
public partial class ViewC : UserControl
-
{
-
public ViewC(ILoggerFacade logger)
-
{
-
InitializeComponent();
-
logger.Log("创建一个ViewC", Category.Info, Priority.Low);
-
}
-
}
-
}
下一步是添加该项目的Module文件,新建一个NavDemoModule类:
-
using System;
-
using System.Net;
-
using System.Windows;
-
using System.Windows.Controls;
-
using System.Windows.Documents;
-
using System.Windows.Ink;
-
using System.Windows.Input;
-
using System.Windows.Media;
-
using System.Windows.Media.Animation;
-
using System.Windows.Shapes;
-
using Microsoft.Practices.Prism.Modularity;
-
using Microsoft.Practices.Prism.Regions;
-
using Microsoft.Practices.Unity;
-
using Microsoft.Practices.Prism.Logging;
-
using Infrastrcture;
-
namespace NavigationDemoModule
-
{
-
public class NavDemoModule:IModule
-
{
-
IRegionManager _regionManager;
-
IUnityContainer _container;
-
ILoggerFacade logger;
-
public NavDemoModule(IUnityContainer container,IRegionManager regionManager,ILoggerFacade logger)
-
{
-
_container = container;
-
_regionManager = regionManager;
-
this.logger = logger;
-
}
-
//如果一个页面相对来说不大变化,如导航区,在程序初始化的过程完成后就不会轻易变动,
-
//这时候就比较适合使用RegisterViewWithRegion方法,通常可以在Module的Initialize方法中完成这个过程
-
public void Initialize()
-
{
-
logger.Log("初始化Navigation模块", Category.Debug, Priority.Low);
-
_regionManager.RegisterViewWithRegion(RegionNames.NavRegion, typeof(NavigationContainer));
-
_regionManager.RegisterViewWithRegion(RegionNames.MainRegion, () => _container.Resolve<NavigationContainer>());
-
_regionManager.RegisterViewWithRegion(RegionNames.NavDemoActionRegion, typeof(ActionController));
-
// 注意注册的类型的必须是object,因为Prism无法确定视图的类型,所以就用了object
-
_container.RegisterType<object, ViewA>(ViewNames.ViewA);
-
_container.RegisterType<object, ViewB>(ViewNames.ViewB);
-
_container.RegisterType<object, ViewC>(ViewNames.ViewC);
-
}
-
}
-
}
10. 最后我们来补全PrismRegionDemo里的内容,先添加一个类Bootstrapper:
-
using System;
-
using System.Windows;
-
using Microsoft.Practices.Prism.UnityExtensions;
-
using Microsoft.Practices.Prism.Logging;
-
using Microsoft.Practices.Prism.Modularity;
-
using Microsoft.Practices.Unity;
-
using Infrastrcture;
-
namespace PrismRegionDemo
-
{
-
public class Bootstrapper : UnityBootstrapper
-
{
-
CallBackLogger logger = new CallBackLogger();
-
protected override ILoggerFacade CreateLogger()
-
{
-
return logger;
-
}
-
protected override IModuleCatalog CreateModuleCatalog()
-
{
-
return Microsoft.Practices.Prism.Modularity.ModuleCatalog.
-
CreateFromXaml(new Uri("/PrismRegionDemo;component/ModulesCatalog.xaml", UriKind.Relative));
-
}
-
protected override void ConfigureContainer()
-
{
-
base.ConfigureContainer();
-
this.Container.RegisterInstance<CallBackLogger>(logger);
-
}
-
protected override DependencyObject CreateShell()
-
{
-
return Container.TryResolve<PrismRegionShell>();
-
}
-
protected override void InitializeShell()
-
{
-
App.Current.RootVisual = (UIElement)Shell;
-
}
-
}
-
}
在添加一个Silverlight REsource Dictionary——ModulesCatalog.xaml:
-
<Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
xmlns:sys="clr-namespace:System;assembly=mscorlib"
-
xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism">
-
<Modularity:ModuleInfo Ref="NavigationDemoModule.xap" ModuleName="NavigationDemoModule"
-
ModuleType="NavigationDemoModule.NavDemoModule, NavigationDemoModule,
-
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
-
<Modularity:ModuleInfo Ref="EmptyDemoModule.xap" ModuleName="EmptyDemoModule"
-
ModuleType="EmptyDemoModule.EmptyModule, EmptyDemoModule,
-
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
-
</Modularity:ModuleCatalog>
最后我们还要修改App.xaml.cs里的启动信息:
-
private void Application_Startup(object sender, StartupEventArgs e)
-
{
-
Bootstrapper bootstrapper = new Bootstrapper();
-
bootstrapper.Run();
-
}