从PRISM开始学WPF(五)MVVM(一)ViewModel?
从PRISM开始学WPF(六)MVVM(二)Command?
从PRISM开始学WPF(七)MVVM(三)事件聚合器EventAggregator?
0x3 Region
regions
Regions是应用程序UI的逻辑区域,它很像一个PlaceHolder,Views在Regions中展现,很多种控件可以被用作Region :ContentControl、ItemsControl、ListBox、TabControl。
简单的说,就是一个容器(区域适配器),用来装载Views的。这像WinForms中的Container控件Panel,里面可以放置其他控件。在PRISM中,Views也是用户控件(UserControl)
Region的注册方式:
在MainWindow.xaml
中:
<Window x:Class="Regions.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Title="Shell" Height="350" Width="525">
<Grid>
<ContentControl prism:RegionManager.RegionName="ContentRegion" />
</Grid>
</Window>
从这里开始,我们就不得不面对 XAML了。继续引用维基百科的介绍:
XAML(Extensible Application Markup Language)是Windows Presentation Foundation(WPF)的一部分,是微软开发的一种基于XML、基于声明,用于初始化结构化值和对象的用户界面描述语言,它有着HTML的外观,又揉合了XML语法的本质,例如:可以使用
<Button>
标签设置按钮Button。
(⊙﹏⊙)。。。
好,这个很简单,不需要介绍了。然后我们看代码,首先在头部引入命名空间xmlns:prism="http://prismlibrary.com/"
,这个prism是别名,你也可以像上面的x
一样将他命名成其他你想要的名字比如sm
,当然了我们默认使用prism这样比较符合命名规范,然后在Grid控件里面,新建了了一个ContentControl,作为Region的目标,并且给他取了一个名字ContentRegion
。
私人定制Region控件
是不是所有控件都可以被用作Region了吗?我们来试一下,为将上面没有列出的StackPanel指定Region,上面Gird块的代码变成这样:
<Grid>
<ContentControl prism:RegionManager.RegionName="ContentRegion" />
<StackPanel prism:RegionManager.RegionName="ContentRegion2" />
</Grid>
似乎看上去一切正常,让我们来启动他。
Oops!!!程序并没有按照我们想象的那样启动,而是抛给了我们一个异常:
Prism.Regions.UpdateRegionsException: 'An exception occurred while trying to create region objects.
Prism在生成一个Region对象的时候报错了,看上去,StackPanel并不支持用作Region。那么有其他的方法让他可以被用作Region吗?因为我们很喜欢用StackPanel啊( ﹁ ﹁ ) ~→(不要管我为什么喜欢,你不,我就偏要) 这难不倒Prism,毕竟创建Region对象就是他自己的事情,做分内的事应该没问题的。
你需要为一个将被用作Region的添加RegionAdapter(适配器)。RegionAdapter的作用是为特定的控件创建相应的Region,并将控件与Region进行绑定,然后为Region添加一些行为。一个RegionAdapter需要实现IRegionAdapte接口,如果你需要自定义一个RegionAdapter,可以通过继承RegionAdapterBase类来省去一些工作。
那,为什么ContentControl就可以呢?因为:
The Composite Application Library provides three region adapters out-of-the-box:
- ContentControlRegionAdapter. This adapter adapts controls of type System.Windows.Controls.ContentControl and derived classes.
- SelectorRegionAdapter. This adapter adapts controls derived from the class System.Windows.Controls.Primitives.Selector, such as the System.Windows.Controls.TabControl control.
- ItemsControlRegionAdapter. This adapter adapts controls of type System.Windows.Controls.ItemsControl and derived classes.
因为这三个是内定的(蛤蛤),就是已经帮你实现了RegionAdapter。接下来,我们看看怎么为StackPanel实现RegionAdapter。
- step1 新建一个类
StackPanelRegionAdapter.cs
,继承RegionAdapterBase
using Prism.Regions;
using System.Windows;
using System.Windows.Controls;
namespace Regions.Prism
{
public class StackPanelRegionAdapter : RegionAdapterBase<StackPanel>
{
public StackPanelRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory)
: base(regionBehaviorFactory)
{
}
protected override void Adapt(IRegion region, StackPanel regionTarget)
{
region.Views.CollectionChanged += (s, e) =>
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement element in e.NewItems)
{
regionTarget.Children.Add(element);
}
}
//handle remove
};
}
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
}
}
- setp2 在
Bootstrapper
注册绑定
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
RegionAdapterMappings mappings = base.ConfigureRegionAdapterMappings();
mappings.RegisterMapping(typeof(StackPanel), Container.Resolve<StackPanelRegionAdapter>());
return mappings;
}
这个具体中间是怎么个道道道,先不管,因为那要看Prism的源码了。不管怎样,我们现在可以为StackPanel
实现用作Region了。我们再运行刚才抛给我们异常的程序,是不是已经跑起来了呢?
注册视图
指定了Region,接下来就为Region指定View了。
在Views目录下面,新建一个UserControl,并在Grid控件内,加一个TextBlock,写上字,标识这是View A。
依旧省去了大部分头部引用,代码都是在Prism-Samples-Wpf里的,这里贴出来仅方便大家无源码阅读
<UserControl x:Class="ViewDiscovery.Views.ViewA"
...
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="View A" FontSize="38" />
</Grid>
</UserControl>
我们现在想要,在上一小节制定的Region内显示View A,怎么做呢?我们来看MainWindow的code-behind,即:MainWindow.xaml.cs
using Prism.Regions;
using System.Windows;
namespace ViewDiscovery.Views
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow(IRegionManager regionManager)
{
InitializeComponent();
//view discovery
regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
}
}
}
看上去,他只用了一行代码就实现了,在Region中显示ViewA。可仔细看一下,这好像哪不太一样。最早我们的MainWindow.xaml.cs的构造函数是没有参数的,这里多了一个IRegionManager
类型的参数regionManager,是不是很眼熟?
依赖注入容器
prism是使用依赖注入容器实现依赖注入的,这里我们使用的是Unity,Prism同样支持Mef容器实现依赖注入。
如果使用 Unity 实例化一个类,该类的构造函数依赖一个或多个其他类,则 Unity 会为构造函数自动创建参数中指定的被依赖的类的实例。上面MainWindow的构造函数中的regionManager参数,就是Unity自动创建的。
RegionManager
代码中RegionManager的RegisterViewWithRegion方法将我们的视图(View)和区域适配器(Region)进行关联
RegionManager,它实现了IRegionManager接口。IRegionManager接口包含一个只读属性Regions,是Region的集合(这个集合是从xaml中获取的,也就是我们定义的那些),RegionManager的实例会使用他们,并将view注册给他们。
View Discovery和View Injection
在Prism中有两种方式来定义视图与Region之间的映射关系——View Discovery和View Injection。
上面的regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
就是View Discovery。
然后我们看一下View Injection:
using Microsoft.Practices.Unity;
using Prism.Regions;
using System.Windows;
namespace ViewInjection.Views
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
IUnityContainer _container;
IRegionManager _regionManager;
public MainWindow(IUnityContainer container, IRegionManager regionManager)
{
InitializeComponent();
_container = container;
_regionManager = regionManager;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var view = _container.Resolve<ViewA>();
IRegion region = _regionManager.Regions["ContentRegion"];
region.Add(view);
}
}
}
他从_regionManager中获取Region,然后使用IRegion.Add(View)的方式来向已有的Region中添加View。
视图的激活与取消激活
private void Button_Click(object sender, RoutedEventArgs e)
{
//activate view a
_region.Activate(_viewA);
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
//deactivate view a
_region.Deactivate(_viewA);
}