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 => 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