Blendable MVVM ViewModelLocator 使用 MEF






4.43/5 (6投票s)
使用动态和 ITypedList & TypeDescriptionProvider 的 ViewModel 定位器新方法
引言
最近我进入了奇妙的 MVVM 世界。关于这个主题有很多内容需要阅读(我已经阅读了),并且有几个非常好的框架可用。所以我从这些框架中“收集”了方便的片段,以便创建我的第一个 MVVM 应用程序。
在这个过程中,我发现 ViewModel 定位器受到了很多讨论,并且各种解决方案都有其优缺点(参见:Nikhil Kothari 关于将视图连接到其模型的选项)。自从 .NET Framework 4 中引入 MEF 以来,一种新的可能性出现了,这由 (johnpapa:MVVM 的简单 ViewModel 定位器:病人离开了疯人院) 描述。
基本上,问题是如何将你的 ViewModel “连接”到你的视图,而无需在多个地方指定它,并保留 Blend 和 VS2010 中的设计时绑定。
因为代码比文章更能说明问题,所以我包含了一个简单的项目,它演示了它是如何工作的。
动态对象
我解决方案的基石是 .NET Framework 4 中引入的新DynamicObject
。如果你想了解更多关于这些工作原理的信息,你可以在此处找到它:一个多层 C# 4.0 动态对象。当从DynamicObject
使用属性时,会使用请求的属性名称调用TryGetMember
。当指定DataContext
时,属性名称可以这样指定
DataContext="{Binding Demo1, Source={StaticResource Locator}}"
由于我的定位器是一个DynamicObject
,因此使用参数名称“Demo1
”调用TryGetMember
。最初,我根据这个名称创建了相应的ViewModel
,如下所示
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string name = binder.Name;
if (!dictionary.TryGetValue(name, out result))
{
switch (name)
{
case "Demo1":
dictionary[name] = (result = new Demo1ViewModel());
return true;
}
return false;
}
return true;
}
但是当我阅读 JohnPapa 的文章后,我决定也添加他的 MEF 实现!关于这个主题的许多文章也可以在这里找到:.NET 4.0 MEF 常见问题解答。为了找到ViewModel
,引入了ExportViewModel
属性
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class ExportViewModel : ExportAttribute
{
public string Name { get; private set; }
public ExportViewModel(string name, bool isStatic)
: base("ViewModel")
{
Name = name;
}
}
它可以像这样用在ViewModel
上
[ExportViewModel("Demo1", false)]
class Demo1ViewModel : ViewModel
{
public Demo1ViewModel()
{
EnDisable = new RelayCommand(() =>
{
isEnabled = !isEnabled;
Action.RaiseCanExecuteChanged();
});
Action = new RelayCommand(() =>
MessageBox.Show("demo1 test"), () => isEnabled);
}
private bool isEnabled = false;
public RelayCommand EnDisable { get; set; }
public RelayCommand Action { get; set; }
}
这里基本上发生的是,在 MEF 的帮助下,ViewModel
获得了一个名称“Demo1
”,并且可以使用CompositionContainer
像这样找到它
[ImportMany("ViewModel", AllowRecomposition = true)]
private IEnumerable<Lazy<object, IViewModelMetadata>> ViewModels { get; set; }
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(typeof(ViewModelLocator).Assembly));
CompositionContainer _container = new CompositionContainer(catalog);
var compositionContainer = new CompositionContainer(catalog);
compositionContainer.ComposeParts(this);
这种方法的最大优势在于,除了这一点之外,无需维护View
和ViewModel
之间的链接,定位器对于项目中的所有ViewModel
都是通用的!TryGetMember
将如下所示
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string name = binder.Name;
if (!dictionary.TryGetValue(name, out result))
try
{
if (ViewModels == null)
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog
(typeof(ViewModelLocator).Assembly));
CompositionContainer _container =
new CompositionContainer(catalog);
var compositionContainer =
new CompositionContainer(catalog);
compositionContainer.ComposeParts(this);
}
dictionary[binder.Name] = (result = ViewModels.Single
(v => v.Metadata.Name.Equals(name)).Value);
return result != null;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return true;
}
设计时绑定
有很多可用的定位器也可以处理与我的定位器相同的函数,但是大多数定位器在可混合性方面存在问题,其中ViewModel
的属性不会显示在 Blend 或 VS2010 中。因为我非常喜欢这个功能,所以我尝试添加一些我在过去使用过的内容,当时我们所有的数据库类都是DataSet
和DataTable
。ITypedList
用于指定哪些属性“实际上不存在” - 请参见:为虚拟属性实现 ITypedList。结合可以用于支持动态运行时属性的 TypeDescriptionProvider,所有以这种方式创建的ViewModel
都是可绑定的。

关注点
通过阅读所有关于 MVVM 的各种文章,我学到了很多东西。我希望这篇文章能帮助其他人。
历史
- 2010 年 10 月 6 日:第一个版本