65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2011年1月20日

CPOL

3分钟阅读

viewsIcon

19717

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

在我之前的帖子中,我介绍了 Blendability 的概念,并解释了如何利用 Blend 的示例数据生成来支持设计时的视图模型。

在这篇文章中,我想继续这个概念,并揭示我对 Prism 模块的设计时支持所做的一点研究。

如果您曾经使用 Prism 开发过 WPF 组合应用程序,您可能意识到在设计时尝试使用 Shell 时会遇到一个令人沮丧的问题。它总是以这样的结果结束

image

正如您所看到的,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();
        }
    }
}

此设计时引导程序提供以下功能

  1. DesigntimeCatalog 类型的特殊 Catalog 属性。 这是一个 MEF 目录的适配器,因此我们可以直接从 XAML 创建设计时目录。
  2. ISupportInitialize 接口的实现,以确保在执行操作之前从 XAML 初始化所有属性。
  3. 用于直接从 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 类型的实例,将其配置为加载两个模块:一个常规模块,以及另一个仅为设计时创建的模块。

既然一切准备就绪,让我们回到我们开始的地方。 我们必须从 RegionManagerOnSetRegionNameCallback 方法中删除设计时条件。

由于 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 以使用区域适配器,看看设计时会发生什么。

imageimage

伙计们,就是这样,我们完成了! 现在我们可以在设计时观看整个故事。

更新 #1:已将 RegionManager 更改为 RegionAdapter 以防止混淆。

这里是代码。

在下一篇文章中,我将讨论使用 MEF 替换视图模型定位器,为设计时提供一个干净整洁的解决方案。

祝大家新年快乐。

© . All rights reserved.