【问题标题】:How are Views injected into the UI using PRISM and MEF?如何使用 PRISM 和 MEF 将视图注入 UI?
【发布时间】:2013-02-23 17:06:56
【问题描述】:

我已经搜索了一些教程,甚至查看了复数网站 PRISM 简介。但是,大多数示例都基于使用统一容器,并且缺少有关如何使用 Mef 容器实现此功能的信息。 我的简单 helloworld 模块基于web tutorial。我的代码是一样的,只是我只停留在 HelloModule 上并使用 Mef,而不是 Unity,如教程所示:

我的主要问题是如何使用我的视图模型初始化我的视图。我通过实验发现的唯一工作方法是在 View 构造函数中初始化视图模型:

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 是一个好方法。你这样做有什么问题?
  • 使用 Unity,他们通过容器初始化视图模型。它让我在 mef 中搜索等效的方式?但如果你说得好,我不会太拘谨,谢谢。

标签: mvvm prism mef


【解决方案1】:

MatthiasG 展示了在 MEF 中定义模块的方法。请注意,视图本身并没有实现 IModule。但是,将 MEF 与 PRISM 结合使用的有趣之处在于如何在启动时将模块导入您的 UI。

我在这里只能从原理上解释系统,但它可能会为您指明正确的方向。每件事都有很多方法,但这是我理解的最佳实践,也是我在这方面取得了非常好的经验:

引导

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

导出视图(模块)

这是 MatthiasG 所指的部分。我的做法是 GUI 模块的结构如下:

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

  • ViewModel 像模型一样将自身导出为具体类型,并通过构造函数注入导入模型:

    [导入构造函数] 公共类 MyViewModel(MyModel 模型) { _model = 模型; }

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

  • 现在,这很重要:视图使用特定属性导出自身,该属性派生自“标准”[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;

这会从容器中导入所有注册为UserControl 的类型,如果它们具有实现IViewRegionRegistration 的元数据属性。因为您的[ViewExport] 属性确实如此,这意味着您导入了每个标有[ViewExport(...)] 的类型。

最后一步是将 View 插入区域,bahvior 在其 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 =&gt; 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's screencast

【讨论】:

  • 嗨,马克,写得很好。我只是想开始使用 MEF 和 PRISM,老实说,我完全迷路了。我找不到任何好的基础教程来指导您完成所有这些,MSDN 仅涵盖零碎的内容,示例项目太复杂了,我无法在不知道我在看什么的情况下进行操作。你知道有什么好的 MEF 和 PRISM 教程可以指点我吗?
  • @user1122909 你喜欢她,太好了。你看过 Mike Taulty 的截屏视频吗(链接在我的帖子中)?花几个小时,你真的会学到很多东西。我基本上是通过这些视频学习 PRISM/MEF 的,仅此而已。 PRISM 手册也可以查找,但我不会对此抱有太高的期望……
  • 不去看看,非常感谢。与此同时,我想你对我发布的一个问题没有任何想法:MEF/Prism? stackoverflow.com/questions/19526287/…
  • 哦,感谢这个描述性的答案。现在这对我来说很有意义!
【解决方案2】:

您实现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
{
}

否则它看起来像这样:

[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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-13
    • 1970-01-01
    • 2011-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多