Prism教程四: Region

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();
        }


 源代码 : http://download.csdn.net/detail/eric_k1m/6216167

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值