mvvm - 如何使用 PRISM 和 MEF 将 View 注入(inject) UI?

标签 mvvm prism mef

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

我的主要问题是如何用我的 View 模型初始化我的 View 。我通过实验发现的唯一工作方法是在 View 构造函数中初始化 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>());
        }
    }

但是,有人可以告诉正确的方法如何做这件事,我这必须在模块初始化部分完成。

最佳答案

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

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

自举

与 Prism 和 Unity 一样,这一切都始于 Bootstrapper,它源自 MefBootstrapperMicrosoft.Practices.Prism.MefExtensions . Bootstrap 设置 MEF 容器并因此导入所有类型,包括服务、 View 、ViewModel 和模型。

导出 View (模块)

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

  • 模型将自身导出为其具体类型(也可以是接口(interface),参见 MatthiasG),使用 [Export(typeof(MyModel)]属性。用 [PartCreationPolicy(CreationPolicy.Shared)] 标记表示只创建一个实例(单例行为)。
  • ViewModel 像模型一样将自身导出为其具体类型,并通过构造函数注入(inject)导入模型:

    [导入构造函数]
    公共(public)类 MyViewModel(MyModel 模型)
    {
    _model = 模型;
    }
  • View 通过构造函数注入(inject)导入 ViewModel,与 ViewModel 导入 Model
  • 的方式相同
  • 现在,这很重要: View 导出自身具有特定属性,该属性源自“标准”[Export]属性。这是一个例子:

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

    [ViewExport] 属性
    [ViewExport]属性做了两件事:因为它派生自 [Export]属性,它告诉 MEF 容器导入 View 。作为什么?这隐藏在它的定义中:构造函数签名如下所示:

    public ViewExportAttribute() : base(typeof(UserControl)) {}
    

    通过调用 [Export] 的构造函数类型为 UserControl ,每个 View 都注册为 UserControl在 MEF 容器中。

    其次,它定义了一个属性 RegionName稍后将用于决定应将 View 插入 Shell UI 的哪个区域。 RegionName 属性是接口(interface)的唯一成员 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; }
    }
    

    导入 View

    现在,系统的最后一个关键部分是行为,您将其附加到 shell 的区域:AutoPopulateExportedViews行为。这将使用以下行从 MEF 容器中导入所有模块:

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

    这将导入注册为 UserControl 的所有类型来自容器,如果他们有一个元数据属性,它实现 IViewRegionRegistration .因为您的[ViewExport]属性确实如此,这意味着您导入所有标有 [ViewExport(...)] 的类型.

    最后一步是将 View 插入到区域中,bahvior 在其 OnAttach() 中执行此操作。属性(property):

    /// <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 属性来仅获取为特定区域导出的那些 View ,您要将行为附加到。

    该行为会附加到 Bootstrap 中的 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

    关于mvvm - 如何使用 PRISM 和 MEF 将 View 注入(inject) UI?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15289178/

    相关文章:

    mvvm - 另一个 MVVM 问题......我的理解是否正确?

    c# - 如何从未由 MEF 容器实例化的对象中导出部件

    .net - MEF 与 Mono.AddIn

    wpf - 模块化 WPF 应用程序的框架?

    configuration - MEF 插件有自己的配置文件吗?

    ios - 在 MVVM 模式下将数据传递给 UIViewController

    c# - MVVM 中异步命令的自动化测试

    c# - 我可以在 DbContext 的 ViewModel 中使用依赖注入(inject)吗?核心2.0

    c# - Microsoft Prism - 外壳窗口中的多个区域

    wpf - 在 WPF Prism 中将模型与 ViewModel 绑定(bind)