65.9K
CodeProject 正在变化。 阅读更多。
Home

Blendable MVVM ViewModelLocator 使用 MEF

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.43/5 (6投票s)

2010年10月6日

CPOL

3分钟阅读

viewsIcon

43995

downloadIcon

1495

使用动态和 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);

这种方法的最大优势在于,除了这一点之外,无需维护ViewViewModel之间的链接,定位器对于项目中的所有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 中。因为我非常喜欢这个功能,所以我尝试添加一些我在过去使用过的内容,当时我们所有的数据库类都是DataSetDataTableITypedList用于指定哪些属性“实际上不存在” - 请参见:为虚拟属性实现 ITypedList。结合可以用于支持动态运行时属性的 TypeDescriptionProvider,所有以这种方式创建的ViewModel都是可绑定的。

blend1.png

关注点

通过阅读所有关于 MVVM 的各种文章,我学到了很多东西。我希望这篇文章能帮助其他人。

历史

  • 2010 年 10 月 6 日:第一个版本
© . All rights reserved.