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

use*_*528 6 prism mef mvvm

我已经搜索了一些教程,甚至看了多元化的PRISM简介.但是,大多数示例都基于使用统一容器和一些缺少有关如何使用Mef容器实现此功能的信息.我的简单helloworld模块基于Web教程.我的代码是相同的,除了我只停留在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; }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

和标准模块初始化代码:

[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>());
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

Mar*_*arc 11

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

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

引导

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

导出视图(模块)

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

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

  • ViewModel将其自身导出为与模型类似的具体类型,并通过构造函数注入导入Model:

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

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

  • 现在,这很重要:View使用特定属性导出自身,该属性派生自"标准" [Export]属性.这是一个例子:

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

[ViewExport]属性

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

public ViewExportAttribute() : base(typeof(UserControl)) {}
Run Code Online (Sandbox Code Playgroud)

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

其次,它定义了一个属性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; }
}
Run Code Online (Sandbox Code Playgroud)

导入视图

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

[ImportMany] 
private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews;
Run Code Online (Sandbox Code Playgroud)

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

最后一步是将Views插入区域,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;
}
Run Code Online (Sandbox Code Playgroud)

注意事项.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));
}
Run Code Online (Sandbox Code Playgroud)

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

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

Mike Taulty的截屏视频


Mat*_*asG 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
{
}
Run Code Online (Sandbox Code Playgroud)

否则它会看起来像这样:

[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
{
}
Run Code Online (Sandbox Code Playgroud)

另一件事:如果您不想更改Views或提供它们的多个实现,则不需要它们interface