Blendability 第二部分 – Prism 的设计时支持





5.00/5 (4投票s)
Blendability 第二部分 – Prism 的设计时支持
在我之前的帖子中,我介绍了 Blendability 的概念,并解释了如何利用 Blend 的示例数据生成来支持设计时的视图模型。
在这篇文章中,我想继续这个概念,并揭示我对 Prism 模块的设计时支持所做的一点研究。
如果您曾经使用 Prism 开发过 WPF 组合应用程序,您可能意识到在设计时尝试使用 Shell 时会遇到一个令人沮丧的问题。它总是以这样的结果结束
正如您所看到的,Shell 始终是空白的,并且由于在设计时未加载任何模块,因此无法正确地进行设计,并且您没有全局视角。
要解决此问题,您只需强制 Blend 或 Visual Studio 设计器加载缺少的模块。
一个好的起点是 RegionManager
类。 您可能注意到,我们通过设置 RegionManager.RegionName
附加属性来使用 RegionManager
将 WPF 控件“标记”为区域。
深入研究 RegionManager
,您可以看到以下代码行
public static readonly DependencyProperty RegionNameProperty =
DependencyProperty.RegisterAttached(
"RegionName",
typeof(string),
typeof(RegionManager),
new PropertyMetadata(OnSetRegionNameCallback));
private static void OnSetRegionNameCallback(
DependencyObject element,
DependencyPropertyChangedEventArgs args)
{
if (!IsInDesignMode(element))
{
CreateRegion(element);
}
}
查看 RegionNameProperty
附加属性的定义,有一个 OnSetRegionNameCallback
属性更改回调。 此方法实际上完成了这项工作。 但是正如您所看到的,如果您处于设计时,它根本不做任何事情。 删除此条件会在设计时产生异常,这是由缺少服务定位器引起的。
所以现在我们正在处理如何强制创建服务定位器,以及许多其他服务,例如 IEventAggregator
。
在这里,让我介绍 Designtime Bootstrapper。 这是一个特殊的 Prism 引导程序,我们将仅用于设计时模块,可以轻松地从 XAML 创建。
现在,在我开始之前,让我们先谈谈模块的运行时和设计时活动之间的区别。
在设计时工作,像运行时一样加载模块是不合理的。 可能存在特殊的模块配置和运行时活动,例如服务器调用,这些活动不适合在设计时进行。 在这里,您应该考虑仅为设计时开发模块。 但这只是我友好的建议,而不是强制性的解决方案。
现在让我们继续使用 Designtime Bootstrapper。 从 Prism 4 开始,有两种加载模块的选项:Unity 和 MEF。 在这篇文章中,我将专注于这两种选择中的一种,当然是 MEF。
使用 MEF 加载模块,您应该创建一个 MefBootstrapper
。
这是我的设计时 MEF 引导程序
[ContentProperty("Catalog")]
public class DesigntimeMefBootstrapper :
MefBootstrapper, ISupportInitialize
{
/// <summary>
/// Gets or sets the design-time catalog.
/// </summary>
public DesigntimeCatalog Catalog
{
get;
set;
}
/// <summary>
/// An empty stub to attach the bootstrapper from XAML.
/// </summary>
public static void SetBootstrapper(
Application application,
DesigntimeMefBootstrapper bootstrapper)
{
}
/// <summary>
/// There is no need for Shell at design-time.
/// </summary>
protected override DependencyObject CreateShell()
{
return null;
}
/// <summary>
/// Use the Catalog added at design time.
/// </summary>
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
if (Catalog != null)
{
AggregateCatalog.Catalogs.Add(Catalog);
}
}
protected override IModuleCatalog CreateModuleCatalog()
{
return new ConfigurationModuleCatalog();
}
/// <summary>
/// Register the container instance so it can be imported later.
/// </summary>
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.ComposeExportedValue<CompositionContainer>(Container);
}
void ISupportInitialize.BeginInit()
{
}
void ISupportInitialize.EndInit()
{
if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
Run();
}
}
}
此设计时引导程序提供以下功能
DesigntimeCatalog
类型的特殊Catalog
属性。 这是一个 MEF 目录的适配器,因此我们可以直接从 XAML 创建设计时目录。ISupportInitialize
接口的实现,以确保在执行操作之前从 XAML 初始化所有属性。- 用于直接从
Application
的 XAML 创建设计时引导程序的存根方法,作为附加属性。
这是我的用于设计时的 MEF 目录适配器之一
public class DesigntimeAssemblyCatalog :
DesigntimeCatalogAdapter<AssemblyCatalog>
{
protected override AssemblyCatalog CreateInnerCatalog()
{
var assembly = Assembly.Load(AssemblyName);
return new AssemblyCatalog(assembly);
}
public string AssemblyName { get; set; }
}
有了这些,让我们跳到 Application
的 XAML 文件中,看看我们如何使用它们。
<Application x:Class="Blendability.Prism.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ts="http://blogs.microsoft.co.il/blogs/tomershamam">
<ts:DesigntimeMefBootstrapper.Bootstrapper>
<ts:DesigntimeMefBootstrapper>
<ts:DesigntimeAggregateCatalog>
<ts:DesigntimeAssemblyCatalog AssemblyName=
"Blendability.Modules.Camera2dT1" />
<ts:DesigntimeAssemblyCatalog AssemblyName=
"Blendability.Modules.DesigntimeModule" />
</ts:DesigntimeAggregateCatalog>
</ts:DesigntimeMefBootstrapper>
</ts:DesigntimeMefBootstrapper.Bootstrapper>
<Application.Resources>
</Application.Resources>
</Application>
如您所见,我直接从 Application
的 XAML 文件创建了一个 DesigntimeMefBootstrapper
类型的实例,将其配置为加载两个模块:一个常规模块,以及另一个仅为设计时创建的模块。
既然一切准备就绪,让我们回到我们开始的地方。 我们必须从 RegionManager
的 OnSetRegionNameCallback
方法中删除设计时条件。
由于 Prism 4 是开源的,我们可以打开 RegionManager
源代码并对其进行更改,但是如果您问我,我宁愿使用适配器而不是更改原始代码,因为谁知道 Prism 的下一个版本会带来什么。
所以这是我的适配器
public class RegionManager
{
public static readonly DependencyProperty RegionNameProperty =
DependencyProperty.RegisterAttached(
"RegionName",
typeof(string),
typeof(RegionManager),
new PropertyMetadata(OnSetRegionNameCallback));
public static void SetRegionName(
DependencyObject regionTarget,
string regionName)
{
if (regionTarget == null)
throw new ArgumentNullException("regionTarget");
regionTarget.SetValue(RegionNameProperty, regionName);
}
public static string GetRegionName(DependencyObject regionTarget)
{
if (regionTarget == null)
throw new ArgumentNullException("regionTarget");
return regionTarget.GetValue(RegionNameProperty) as string;
}
private static void OnSetRegionNameCallback(
DependencyObject element,
DependencyPropertyChangedEventArgs args)
{
Microsoft.Practices.Prism.Regions.RegionManager.SetRegionName(
element, (string)args.NewValue);
if (DesignerProperties.GetIsInDesignMode(element))
{
CreateRegion(element);
}
}
private static void CreateRegion(DependencyObject element)
{
IServiceLocator locator = ServiceLocator.Current;
DelayedRegionCreationBehavior regionCreationBehavior =
locator.GetInstance<DelayedRegionCreationBehavior>();
regionCreationBehavior.TargetElement = element;
regionCreationBehavior.Attach();
}
}
好的,现在让我们更改 shell 以使用区域适配器,看看设计时会发生什么。
伙计们,就是这样,我们完成了! 现在我们可以在设计时观看整个故事。
更新 #1:已将 RegionManager
更改为 RegionAdapter
以防止混淆。
这里是代码。
在下一篇文章中,我将讨论使用 MEF 替换视图模型定位器,为设计时提供一个干净整洁的解决方案。
祝大家新年快乐。