如何使用PRISM和MEF将视图注入到UI中?

6
我已经搜索了一些教程,甚至看了Pluralsite的PRISM介绍。然而,大多数示例都是基于使用Unity容器,有些缺乏如何使用Mef容器实现此功能的信息。
我的简单helloworld模块基于Web教程。我的代码与教程中的相同,只是我卡在了HelloModule上,使用的是Mef,而不是教程中的Unity:
我的主要问题是如何使用我的视图模型初始化我的视图。我找到的唯一有效的方法是在View构造函数中初始化ViewModel。
HelloView.xaml.cs
namespace Hello.View
{
    [Export]
    public partial class HelloView : UserControl, IHelloView
    {
        public HelloView()
        {
            InitializeComponent();
            Model = new HelloViewModel(this);
        }

        public IHelloViewModel Model
        {
            //get { return DataContext as IHelloViewModel; }
            get { return (IHelloViewModel)DataContext; }
            set { DataContext = value; }
        }
    }
}

标准模块初始化代码:

[ModuleExport(typeof(HelloModule), InitializationMode=InitializationMode.WhenAvailable)]
    public class HelloModule : IModule
    {
        IRegionManager _regionManager;

        [ImportingConstructor]
        public HelloModule(IRegionManager regionManager)
        {
            _regionManager = regionManager;
        }

        public void Initialize()
        {
            _regionManager.Regions[RegionNames.ContentRegion].Add(ServiceLocator.Current.GetInstance<HelloView>());
        }
    }

然而,有人能告诉我正确的方法如何做这些事情吗?我认为必须在模块初始化部分完成。

在View构造函数中初始化view-model是一个不错的方式。你这样做有什么问题吗? - thumbmunkeys
使用Unity,他们通过容器初始化视图模型。这让我在MEF中寻找等效的方法?但如果你说这是一个好方法,我就不会太纠结了,谢谢。 - user2147528
2个回答

11

MatthiasG 展示了在 MEF 中定义模块的方法。需要注意的是,视图本身并不实现 IModule 接口。然而,使用 MEF 和 PRISM 的有趣之处在于如何在启动时将模块导入到您的 UI 中。

我只能在这里原则上解释系统,但它可能指向正确的方向。对于任何事情,总是有无数的方法,但这就是我理解为最佳实践并且我有非常好的经验:

引导

与 Prism 和 Unity 一样,一切都始于 Bootstrapper,它派生自 Microsoft.Practices.Prism.MefExtensions 中的 MefBootstrapper。引导程序设置了 MEF 容器,从而导入所有类型,包括服务、视图、ViewModel 和模型。

导出视图(模块)

这是 MatthiasG 提到的部分。我的做法是使用以下结构来处理 GUI 模块:

  • 该模型导出其本身的具体类型(也可以是接口,参见 MatthiasG),使用 [Export(typeof(MyModel)] 属性。使用 [PartCreationPolicy(CreationPolicy.Shared)] 标记表示只创建一个实例(单例行为)。

  • ViewModel 就像模型一样导出其本身的具体类型,并通过构造函数注入导入模型:

    [ImportingConstructor] public class MyViewModel(MyModel model) { _model = model; }

  • View 通过构造函数注入导入 ViewModel,与 ViewModel 导入模型的方式相同。

  • 现在,这很重要:视图使用特定属性导出自己,该属性派生自“标准”[Export] 属性。以下是一个示例:

[ViewExport(RegionName = RegionNames.DataStorageRegion)]
public partial class DataStorageView
{
    [ImportingConstructor]
    public DataStorageView(DataStorageViewModel viewModel)
    {
        InitializeComponent();
        DataContext = viewModel;
    }
}

[ViewExport] 属性

[ViewExport] 属性有两个作用:由于它派生自 [Export] 属性,它告诉 MEF 容器导入视图。作为什么?这隐藏在它的定义中:构造函数签名如下:

public ViewExportAttribute() : base(typeof(UserControl)) {}
通过使用[Export]的构造函数并指定UserControl类型,每个视图都将在MEF容器中注册为UserControl
其次,它定义了一个属性RegionName,该属性将用于确定视图应插入Shell UI的哪个区域。RegionName属性是接口IViewRegionRegistration的唯一成员。这个特性类:
/// <summary>
/// Marks a UserControl for exporting it to a region with a specified name
/// </summary>
[Export(typeof(IViewRegionRegistration))]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
[MetadataAttribute]
public sealed class ViewExportAttribute : ExportAttribute, IViewRegionRegistration
{
    public ViewExportAttribute() : base(typeof(UserControl)) {}

    /// <summary>
    /// Name of the region to export the View to
    /// </summary>
    public string RegionName { get; set; }
}

导入视图

现在,系统的最后一个关键部分是一种行为,它可以附加到您的 shell 的区域上: AutoPopulateExportedViews 行为。此行代码将从 MEF 容器中导入您的模块:

[ImportMany] 
private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews;

如果容器中的类型拥有实现了IViewRegionRegistration接口的元数据属性,并且已经注册为UserControl类型,则此处导入所有类型。因为您的[ViewExport]属性是这样的,这意味着您会导入每个标有[ViewExport(...)]的类型。

最后一步是将视图插入到区域中,在其OnAttach()属性中完成此操作:

/// <summary>
/// A behavior to add Views to specified regions, if the View has been exported (MEF) and provides metadata
/// of the type IViewRegionRegistration.
/// </summary>
[Export(typeof(AutoPopulateExportedViewsBehavior))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class AutoPopulateExportedViewsBehavior : RegionBehavior, IPartImportsSatisfiedNotification
{
    protected override void OnAttach()
    {
        AddRegisteredViews();
    }

    public void OnImportsSatisfied()
    {
        AddRegisteredViews();
    }

    /// <summary>
    /// Add View to region if requirements are met
    /// </summary>
    private void AddRegisteredViews()
    {
        if (Region == null) return;

        foreach (var view in _registeredViews
            .Where(v => v.Metadata.RegionName == Region.Name)
            .Select(v => v.Value)
            .Where(v => !Region.Views.Contains(v)))
            Region.Add(view);

    }

    [ImportMany()] 
    private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews;
}

注意 .Where(v => v.Metadata.RegionName == Region.Name)。这使用属性的 RegionName 来获取仅导出到特定区域的视图,您正在将行为附加到该区域。

在引导程序中,行为会附加到 shell 的区域:

protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors()
{
    ViewModelInjectionBehavior.RegionsToAttachTo.Add(RegionNames.ElementViewRegion);

    var behaviorFactory = base.ConfigureDefaultRegionBehaviors();
    behaviorFactory.AddIfMissing("AutoPopulateExportedViewsBehavior", typeof(AutoPopulateExportedViewsBehavior));
}

我们已经来了一圈,我希望这能让你了解MEF和PRISM是如何安排好一切的。

如果你还没有厌倦:

Mike Taulty的视频演示


嗨,马克,写得很好。我正在尝试开始使用MEF和PRISM,老实说,我完全迷失了。我找不到任何好的基础教程,可以带你完成所有这些步骤,MSDN只涵盖了一些部分,示例项目对我来说太复杂了,无法在不知道自己在看什么的情况下进行跟随。你知道有关MEF和PRISM的任何好教程吗?你能指点我吗? - NZJames
@user1122909 很高兴听到你喜欢它。你看过Mike Taulty的Screencast了吗(链接在我的帖子中)?花几个小时,你会真正学到很多东西。我基本上是通过这些视频学习PRISM/MEF的,没有其他什么了。PRISM手册也可以查阅,但我不会对此抱有太高的期望... - Marc
不会去看了,非常感谢。只是在这段时间里,我不知道您是否对我发布的关于MEF/Prism的问题有任何想法?https://dev59.com/s3jZa4cB1Zd3GeqPcEUT - NZJames
哦,谢谢你这个详细的回答。现在我明白了! - boop

1
你实现 HelloView 的方式意味着 View 必须知道 IHelloViewModel 的确切实现,这在某些情况下是可以的,但这意味着你不需要这个 interface
我提供的示例使用 property injection,但 constructor injection 也可以。
如果你想使用 interface,可以这样实现:
[Export(typeof(IHelloView)]
public partial class HelloView : UserControl, IHelloView
{
    public HelloView()
    {
        InitializeComponent();
    }

    [Import]
    public IHelloViewModel Model
    {
        get { return DataContext as IHelloViewModel; }
        set { DataContext = value; }
    }
}

[Export(typeof(IHelloViewModel))]
public class HelloViewModel : IHelloViewModel
{
}

否则它会像这样:

Otherwise it would look like this:

[Export(typeof(IHelloView)]
public partial class HelloView : UserControl, IHelloView
{
    public HelloView()
    {
        InitializeComponent();
    }

    [Import]
    public HelloViewModel Model
    {
        get { return DataContext as HelloViewModel; }
        set { DataContext = value; }
    }
}

[Export]
public class HelloViewModel
{
}

还有一件事:如果您不想更改您的 Views 或者提供多个实现,那么您就不需要为它们创建一个 interface


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接