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

时间:2013-03-08 07:52:24

标签: mvvm prism mef

我已经搜索了一些教程,甚至看了多个网站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>());
        }
    }

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

2 个答案:

答案 0 :(得分:11)

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

我原则上只能解释系统,但它可能会指向正确的方向。对所有事情总是有很多方法,但这是我所理解的最佳实践以及我所获得的非常好的经验:

<强>自举

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

导出视图(模块)

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

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

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

    [ImportingConstructor] 公共类MyViewModel(MyModel模型) {     _model = model; }

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

  • 现在,这很重要: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]的{​​{1}}构造函数,每个视图都会在MEF容器中注册为UserControl

其次,它定义了一个属性UserControl,稍后将使用该属性来决定应该在哪个Shell UI区域中插入视图。 RegionName属性是接口RegionName的唯一成员。属性类:

IViewRegionRegistration

导入视图

现在,系统的最后一个关键部分是行为,您可以将其附加到shell的区域:/// <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; } } 行为。这将使用以下行从MEF容器导入所有模块:

AutoPopulateExportedViews

如果它们具有实现[ImportMany] private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews; 的元数据属性,则会从容器中导入所有注册为UserControl的类型。由于您的IViewRegionRegistration属性有效,这意味着您导入了标有[ViewExport]的所有类型。

最后一步是将视图插入区域,bahvior在其[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; } 。这使用属性的RegionName属性仅获取为特定区域导出的那些视图,并将行为附加到。

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

.Where(v => v.Metadata.RegionName == Region.Name)

我们希望,这可以让你了解MEF和PRISM如何落实到位。

并且,如果你仍然不感到无聊:这是完美的:

Mike Taulty's screencast

答案 1 :(得分:1)

您实施HelloView的方式意味着View必须知道某些情况下IHelloViewModel的确切实施,但这意味着您不需要interface property injection 1}}。

对于我提供的示例,我使用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或提供多个实施,则不需要{{1}}。