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

MVVM 自制版本

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2010年9月25日

CPOL

14分钟阅读

viewsIcon

26695

downloadIcon

1223

一个简单的代码,展示了如何创建、绑定和注入视图和视图模型

引言

MVVM 的一些方面让我觉得很贴切,我尝试看看构建最简单的 MVVM 需要什么。我从创建、绑定以及我需要的一些基本功能的概念开始。下面的代码很简单、可扩展,并且提供了一种更容易理解 MVVM 概念的方法。我的方法是:MVVM 设计模式是一套建议,而不是一套规则……

脚手架

该项目提供了 MVVM 模式的框架和构建块,以及其他一些基本工具。大部分的“功夫”都发生在“CreateAndInject”文件中,该文件包含一个同名的类。这个类公开了一个名为“CreateAndBindViewAndViewModel”的static方法,该方法实例化对象,绑定它们,最后将它们注入我想要的地方(详情请参阅接下来的 3 个部分)。

Scaffolding项目还支持读取配置数据,它包含一些基本的接口、一些事件、委托和支持数据类,以及初始命令(基本命令,将在下一期中扩展)。

我将从视图-视图模型(V-VM)的定义开始,该定义包含在“ModelViewRelation”类中(在同名文件中)。在这里,我指定了两个类型的名称(使用string)和一个可能的显示名称。为了方便起见,我还允许此定义包含一个对象(在我尝试将数据注入 V-VM 时会很有用)。

VM 和 V 的创建是通过反射完成的,为此我需要(除了类型名称之外) V 和 VM 类型所在的程序集以及类型的命名空间。 “ModelViewRelation”包含这些strings 的默认值;这些值也可以在运行时通过编程方式加载,或在‘ServiceReferences.ClientConfig’文件中配置。

<configuration />
  <appsettings />
	<add key="AssemblyVersion" value="1.0.0.0" //>
	<add key="AssemblyName" value="HomeGrownMVVM" //>
	<add key="ViewModelPath" value="HomeGrownMVVM.ViewModel" //>
	<add key="ViewPath" value="HomeGrownMVVM.View" //>
	<add key="ModelSuffix" value="Model" //>
	<add key="QueryRandomizer" value="3" //>
  </appsettings />

使用这个类很简单

var mvr = new ModelViewRelation("Classic CD's", "ClassicView");

在上面的行中,我隐含地表示 VM 类型是“ClassicViewModel”。如果我想将“ClassicView”绑定到“RockViewModel”,我必须这样做

var mvr = new ModelViewRelation("Classic CD's", "ClassicView"", "RockViewModel");

视图和视图模型的创建

如前所述,繁重的工作是由一个名为“CreateAndBindViewAndViewModel”的static方法完成的,该方法提供了以下 MVVM(以及 MVVM 相关)活动:

  • 创建视图和视图模型 – 通过 CreateViewModelAndView()
  • 绑定(并连接)视图到视图模型 – 通过 WireAndBind()
  • 将视图注入其宿主 – 通过委托: injectView

此方法按顺序调用 3 个private方法 – 每个方法涵盖上述一个步骤。 “CreateAndInject”类中的static方法“CreateAndBindViewAndViewModel”接受 4 个参数,并在所有操作成功时返回true

public static bool CreateAndBindViewAndViewModel(
                ModelViewRelation mvRelation, //defines the View and ViewModel types
                InjectViewDelegate injectView, //call this delegate to inject the view
                UIElement host= null, //optional container for the view 
				//(used by the injector)
                IBaseViewModel substitute = null) //optional (existing) object 
						//to use as the ViewModel
{
    UIElement view;
    //instantiate the objects
    object viewModel = CreateViewModelAndView(mvRelation, substitute, out view);
    //wire the objects
    WireAndBind(mvRelation, view, viewModel);
    if (view == null)
    {
        MessageBox.Show("Fail to launch " + mvRelation.ToString());
        return false;
    }
    return injectView(mvRelation, view, host) != null; //inject the View
}

这里没有魔法……

继续 – 实例化 VM 和 V

private static object CreateViewModelAndView( //returns the created or existing ViewModel
            ModelViewRelation mvRelation, //defines the View and ViewModel types
            IBaseViewModel substitute, //may be used as a ViewModel
            out UIElement view) //returns the created View
{
    view = null;
    Assembly assembly = null;
    object viewModel = null;

    if (mvRelation.IsValid == true)
    {//for security, Silverlight requires the long form of assembly name
        var secureName = string.Format(
            "{0}, Version={1}, Culture=neutral, PublicKeyToken=null",
            mvRelation.AssemblyName,
            mvRelation.AssemblyVersion);
        try
        {//load the assembly
            assembly = Assembly.Load(secureName);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
        if (assembly != null)
        {//create a new VM or use the passed in substitute?
            viewModel = mvRelation.ViewModelName.EndsWith(".this") ? 
                substitute : //the new View will bind to this (passed-in) 
				//existing ViewModel
                CreateTheObject(assembly, mvRelation.ViewModelName); //create a 
							//new ViewModel
            if (viewModel != null)
            {// VM is here, create the View from a given fully qualified type name
                view = CreateTheObject(assembly, mvRelation.ViewName) as UIElement;
            }
        }
    }
    return viewModel;
}

同样,这里也没有奇迹,但有几点值得一提:

如果“CreateAndInject”文件包含在视图和视图模型相同的项目中,则可以使用更简单的调用 Assembly.GetExecutingAssembly()

如果调用此类仅从视图和视图模型相同的项目中进行,则可以使用更简单的调用 Assembly.GetCallingAssembly(),但与 C++ 不同,C# 没有inline关键字;因此,您必须将此方法中的代码合并到main public方法中。

我编写的代码更通用,但有一个缺点是,当包含 V 和 VM 类型的程序集名称或版本发生更改时 – 需要更新配置文件。如果预计有其他更改(区域性等) – 将“完整名称”参数的其余部分移至配置文件。

内置的“回收”VM 功能允许将新视图重新绑定到现有 ViewModel(请参阅“BaseViewMode”中的“InformingView_NotifyEvent”方法的示例)。目前,当我使用该功能时,我不会取消绑定现有视图,因为我从未需要过它 – 但可以这样做。扩展“ModelViewRelation”类非常简单,解绑也应该如此。

实际的实例化发生在“CreateTheObject”方法中。

private static object CreateTheObject(Assembly assembly, string objectName)
{
    object obj = null;
    var objectType = assembly.GetType(objectName);
    if (objectType != null)
    {//found the type in the assembly, instantiate the object
        obj = Activator.CreateInstance(objectType);
    }
    return obj;
}

视图-视图模型绑定

这是由“WireAndBind”方法完成的第二步。

private static void WireAndBind(
            ModelViewRelation mvRelation, //defines the View and ViewModel types
            UIElement view, //the View that needs binding to VM
            object viewModel) //the ViewModel that will be bound to the V
{
    if (view != null)
    {
        if (viewModel is IBaseViewModel)
        {
            if (mvRelation.Anything != null)
            {//inject Data into the ViewModel, if so 'requested'
                (viewModel as IBaseViewModel).ModelData = mvRelation.Anything;
            }
            if (view is IDirectData)
            {//pass the VM a reference of the V for data injecting rather than binding
                (viewModel as IBaseViewModel).DirectDataToView = view as IDirectData;
            }
            //call LoadData() method on the VM
            (viewModel as IBaseViewModel).LoadData();
        }
        if (view is FrameworkElement)
        {//data-bind the View to the ViewModel
            (view as FrameworkElement).DataContext = viewModel;
        }
        if ((view is IInforming) && (viewModel is IBaseViewModel))
        {//allow the View to fire custom NotifyEvent
            (viewModel as IBaseViewModel).Subscribe(view as IInforming);
        }
        if ((view is ILaunchChild) && (viewModel is IBaseViewModel))
        {//allow The View to fire LaunchChildEvent
            (viewModel as IBaseViewModel).Subscribe(view as ILaunchChild);
        }
    }
}

仍然没有奇迹(也许有一两个 MVVM 的“罪过”……),此方法检查声明了哪些接口,并基于此执行某些操作。按出现顺序:

  1. 使用IBaseViewModel接口的一部分ModelData变量将数据注入 VM,该变量位于BaseViewModel类中。仅当用户在ModelViewRelation中设置可选参数‘Anything’时,此操作才会发生(请参阅“BaseViewMode”中的“InformingView_NotifyEvent”方法的示例)。这对于编写通用 ViewModel 很有用。
  2. 将视图的引用传递给 VM 的DirectDataToView变量(我知道,字面意义上的 MVVM 信徒会摇头)。此犯罪/罪恶仅在视图实现IDirectData接口时发生(信徒:不要声明此接口)。当视图公开此接口时,ViewModel可以将List< IModelViewData>推送到视图。但更重要的是,如果ViewModel向上转型DirectDataToView并获得视图的句柄。几乎没有需要使用此接口。
  3. 调用ViewModel内的Load Data方法,允许 VM 在视图“瞥见”下一步将绑定的数据之前执行任何所需的工作。LoadDataIBaseViewModel接口的一部分,虚拟方法位于BaseViewModel中,并且显然可以被任何ViewModel覆盖。BaseViewModel的构造函数调用其自己的Initialize来执行LoadData可以被调用之前的任何活动(例如,准备 WCF 代理)。
  4. 将视图绑定到ViewModel,前提是视图是FrameworkElement
  5. 注册ViewModel以接收NotifyEvent类型的自定义事件。此订阅仅在视图实现IInforming接口时发生。这不是 MVVM 标准,但它允许我快速进行一些测试,克服命令的缺乏,并且通常情况下,如果您不希望打开此通信通道,则不要实现该接口。
  6. 注册ViewModel以接收LaunchChildEvent类型的自定义事件。此订阅仅在视图实现ILaunchChild接口时发生。与之前相同的评论……

“简单”视图注入

这部分有点棘手,只是因为我不知道新的视图要在哪里注入以及如何注入,所以我非常依赖调用者并保持我的生活简单:调用者必须提供一个注入委托给我,我会调用它。注入委托的签名接受 3 个参数并返回一个参数。

public delegate UIElement InjectViewDelegate(	//return the created view 
					//or null if failed
            ModelViewRelation mvRelation, 	//defines the View and ViewModel types
            UIElement boundView, 		//the created View
            UIElement host); 		//hosts the view

为了阐明注入委托,最好看一个例子。为此,我需要花些时间谈谈演示。

TabControl及其TabItem是一个非常好的方式来快速切换不同的文件(Visual Studio)或切换不同的视图。演示将在TabControl中启动视图,每次都创建一个新的TabItem来托管View

对于注入示例,我将查看由侧边菜单创建的视图。以下是演示的欢迎页面。

当点击侧边菜单中的任何链接时,一个视图将被注入到一个新的选项卡中,该选项卡被添加到选项卡控件中,如下一张图片所示。

不难猜到,点击侧边菜单会将填充的“ModelViewRelation”类发送到“CreateAndInject”方法。以下是代码片段:

private void MenuItemSelected(object obj)
{
	var mvRelation = obj as ModelViewRelation;
	if (mvRelation != null)
	{
		CreateAndInject.CreateAndBindViewAndViewModel
	(mvRelation, InjectView, null);
	}
}

如您所见,这里我收到了填充的mvRelation,我将其作为委托传递给InjectView方法,并将宿主留为空(主要是因为宿主尚不存在)。mvRelation在构建侧边菜单时被填充。此“CreateAndInject”方法将实例化 MV 和 V,绑定它们,然后调用即将出现的注入器。

注入器通常比这个简单 – 这个注入器稍微长一些,因为它做了一些比仅仅注入视图更多的事情。稍后会有更简单的例子。来看代码:

private TabItem InjectView(ModelViewRelation mvRelation, UIElement view, UIElement host)
{
	var tabPage = CreateTabPage(mvRelation.DisplayName, view);
	if (tabPage != null)
	{ //this collection is bound to the TabControl, 
	  //hence adding an item adds a TabItem
		TabPagesCollection.Add(tabPage);
		TopTabPage = tabPage; //TopTabPage is bound to the SelectedItem
	}
	UpdateViwingArea(); //Tab controls is visible if it contains 
				//items otherwise collapsed
	return tabPage;
}

private TabItem CreateTabPage(string header, UIElement view)
{
	if (view == null)
	{
		return null;
	}
	//enhance the header: allow closing a TabItem, it displays V-VM name
	var hdr = new TabHeaderControl(header);
	hdr.CloseMeEvent += CloseTabItemEvent; //register for the Close Event
	var tab = new TabItem //create a new TabItem
	{
		Content = view, //INJECT THE VIEW !!!
		Header = hdr, //inject the fancy header
		HorizontalAlignment = HorizontalAlignment.Stretch,
		HorizontalContentAlignment = HorizontalAlignment.Stretch
	};
	hdr.Tag = tab; //let the header have a reference to its own tab (for closing)
	return tab;
}

注入器首先调用“CreateTabPage”来创建一个新的TabItem。 “CreateTabPage”中的代码创建一个用户控件(TabHeaderControl)来提供关闭TabItem的方式。然后它创建TabItem本身并将视图注入到项目的*内容*中 – 正如预期的那样。接下来,它将标题控件放置在标题中。最后,它存储对新选项卡在标题控件中的引用,以便知道要关闭哪个选项卡。

注入器将创建的TabItem添加到TabPagesCollection。此集合绑定到TabControlItemSource;因此,一个新TabItem被添加到TabControl。这个TabItem包含了注入的View。接下来,注入器将TopTabPage设置为新的TabItemTopTabPage绑定到TabControlSelectedItem,因此我刚刚添加的TabItem将位于所有其他选项卡的最上面。最后,它根据其拥有的项目数量更新TabControl的可见性(0 = 折叠,否则可见)。

视图到视图注入

此演示假装是一个查询数据库的工具。因此,我创建了一个 V-VM 来收集、处理和显示经典 CD。在这种情况下,V 包含一个包含所有 CD 的DataGrid,一些状态和摘要文本,以及点击网格中的任何一行以查看特定 CD 的详细视图的能力。

一旦上述 V-VM 工作正常,我想创建一个新的 V-VM 来允许用户执行数据库查询。这个新视图应该包含用户指定查询的方式 – 至少一个文本框 – 和一个提交按钮来运行查询。一旦他运行了查询,结果就会回来,如果我能使用我之前构建的同一个视图,但又不想丢失视图的查询部分 – 我想将这个第一个视图注入到我的新查询视图中,那将非常有帮助。

这很简单。我向我的查询 V 添加一个ContenPresenter,它将是第一个 V 的宿主。这是 XAML(在文件ClassicQueryView.xaml中):

<ContentPresenter Height="Auto"
			Margin="7,7,2,2"
			HorizontalAlignment="Left"
			VerticalAlignment="Top"
			Grid.Row="1"
			Loaded="ContentPresenter_Loaded"
			Tag="[ClassicView]"
			Visibility="{Binding ShowQueryResults}"
			>
</ContentPresenter>

Visibility”已绑定,因此仅在提交查询后才可见。

Loaded事件将触发所请求View-ViewModel的创建、绑定和注入。所请求的 V-VM 使用 Tag 指定。自制格式为[View][ViewModel][View],最后一种格式用于当前ViewModel要绑定到新View时。

当处理Loaded事件时,我从Tag中获取信息,填充 V-VM 关系类,调用忠实的“CreateAndInject”,然后坐下来观看视图被注入。

我将从最后开始,看一下注入委托

private UIElement InjectTheView(ModelViewRelation mvRelation, 
                               UIElement view, 
                               UIElement host)
{
	var presenter = host as ContentPresenter;
	if ((presenter != null) && (view != null))
	{
		presenter.Content = view;
	}
	return view;
}

比这更简单的代码很难找到。您会期望在查询视图的ViewModel中找到这个注入器,但我将其推回到BaseViewModel,只是因为我经常想将一个视图注入另一个视图,这样如果我总是使用ContentPreseter,我就不必编写任何代码 – 只需将其添加到 XAML 并处理事件即可。

回到开始 – 触发了 loaded 事件;我处理了它在查询视图的代码后台(只是因为我还没有命令)。下面是完整的代码后台文件,包括Loaded事件的处理程序(ClassicQueryView.xaml.cs)。

using System.Windows;
using System.Windows.Controls;
using VvmScaffolding;
namespace HomeGrownMVVM.View
{
	public partial class ClassicQueryView : UserControl, IInforming
	{
		public ClassicQueryView()
		{
			InitializeComponent();
		}

		private void ContentPresenter_Loaded(object sender,
                                               RoutedEventArgs e)
		{
			if (NotifyEvent != null)
			{
				NotifyEvent(sender, 
                                          new GenericEventArgs(sender));
			}
		}

		public event NotifyEventHandler NotifyEvent;
	}
}

ClassicQueryView”类实现了 Informing 接口,因此如果它引发NotifyEvent,则绑定的 VM 将处理它。事件处理程序接收发送者并将其放入自定义GenericEventArg

下一步是查看如何处理这个事件,这在ViewModel中完成;我再次将处理程序推送到BaseViewModel,这样我就不必再担心它了。来自BaseViewModel

protected virtual void InformingView_NotifyEvent(object sender, GenericEventArgs e)
{
	var presenter = e.Camel as FrameworkElement;
	if (presenter != null)
	{ //it is  a FrameElement which means it contains Tag
		var names = presenter.Tag as string; //read the Tag
		if (string.IsNullOrEmpty(names) == false)
		{ //it contains a string lets process it
			e.Handled = true; //tell overriding handlers that 
					//I handled the event.
			var separators = new[] {'[', ']', ' '};
			names = names.Trim(separators);
			var saNames = names.Split(separators, 
				StringSplitOptions.RemoveEmptyEntries);
			ModelViewRelation mvr = null;
			switch (saNames.Length)
			{
				case 1: //this VM will bo bound to the View
					mvr = new ModelViewRelation
					("Dynamic View", saNames[0], "this");
					break;
				case 2: //create a new V and VM
					mvr = new ModelViewRelation
					("Dynamic View", saNames[0], saNames[1]);
					break;
			}
			CreateAndInject.CreateAndBindViewAndViewModel
					(mvr, InjectTheView, presenter, this);
		}
	}
}

由于此处理程序在基类中,因此处理该事件的每个类都将覆盖此处理程序。覆盖的处理程序必须首先调用基处理程序,并且只有在基处理程序未处理它时才继续。在从 Tag 中提取名称后,代码将填充ModelViewRelation。如果找到一个名称,它会在 VM 的名称中插入字符串‘this’ – 这将导致“CreateAndInject”跳过实例化 VM。调用“CreateAndInject”将新创建的ModelViewRelation、注入委托(我们之前看过的)、作为宿主的表示器(Loaded事件的发送者)以及其本身作为替换 VM(以防需要替换)传递。

在这种情况下,新 V 的 VM 是现有的“ClassicQueryViewModel”。如果我想让新视图能够启动子视图(请参阅下一节),我需要将该事件的处理程序从“ClassicViewModel”复制到“ClassicQueryViewModel”。

启动子视图

在此演示中,当我点击网格行时,我想要获取该行中 CD 的详细视图。这个详细视图将使用ChildWindow创建。视图的名称是DetailsChildWindowView.xamlViewModel遵循命名约定DetailsChildWindowViewModel.cs

这是 VM 通过启动 VM(我将使用ClassicViewModel作为启动者)间接注入数据的一种情况。另一个不同之处在于,这个 V-VM 没有其他视图宿主,我曾想称之为浮动视图,但ChildWindow可能更接近真相。这个视图没有宿主的事实实际上不是问题,因为注入器会知道该怎么做。

让我概括一下:我点击一行,SelectionChanged事件被触发,事件由代码后台处理,启动一个 V-VM,将选定的行详细信息注入启动的 V-VM,然后显示视图。

这是ClassicView.xaml的 XAML 的DataGrid部分:

<sdk:DataGrid	AutoGenerateColumns="True"
			HorizontalAlignment="Left"
			Margin="5"
			VerticalAlignment="Top"
			Grid.Row="1"
			ItemsSource="{Binding DataToPresent}"
			SelectionChanged="DataGrid_SelectionChanged"
			/>

这是代码后台(ClassicView.xaml.cs)中SelectionChanged事件的处理程序:

private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
	if ((NotifyEvent != null) && (e.AddedItems.Count > 0))
	{
		NotifyEvent(sender, new GenericEventArgs(e.AddedItems[0]));
	}
}

一旦确定有一行被选中,它就将项目(通过 XAML 绑定的PresentingClassicData类)传递给GenericEventArgs并触发NotifyEvent

NotifyEvent由 VM – ClassicViewModel处理。

protected override void InformingView_NotifyEvent(object sender, GenericEventArgs e)
{
	base.InformingView_NotifyEvent(sender, e);
	if (e.Handled == true)
	{ // base class handled this event
		return;
	}
	var r = e.Camel as PresntingClassicData;
	if (r != null)
	{ //this is PresntingClassicData as expected, create ModelViewRelation
		var mvvmDef = new ModelViewRelation("Details", //name
                           "DetailsChildWindowView", //View
                           "DetailsChildWindowViewModel", //ViewModel
                           r.Details); //Injected data
		PopUpView(this, new ModelViewRelationEventArgs(mvvmDef));
	}
	var dg = sender as DataGrid;
	if (dg != null)
	{ //clear selection, so that clicking on same item again will 
	  //create SelectionChange event
		dg.SelectedItems.Clear(); 
	}
}

调用PopUpView来启动视图。PopUpView被推送到BaseViewModel,因此每个 VM 都可以使用它和它的注入器。

public void PopUpView(object sender, ModelViewRelationEventArgs e)
{
	CreateAndInject.CreateAndBindViewAndViewModel(e.Holder, ShowDetails);
}
//The Injector
public ChildWindow ShowDetails(ModelViewRelation mvRelation, 
			UIElement view, UIElement host)
{
	var cw = view as ChildWindow;
	if (cw != null)
	{
	      cw.Show();
	}
	return cw;
}

PopUpView调用CreateAndBindViewAndViewModel。它传递一个注入委托 – ShowDetails。注入器简单地接收视图并确保它是ChildWindow类。一旦是ChildWindow,注入器就会在其上调用Show

获取配置数据

ConfigurationManager.cs”文件提供了一种从ServiceReferences.ClientConfig获取配置数据的方法。我使用配置数据将配置数据传递给应用程序,并创建初始的侧边菜单及其 V-VM 设置。

AppSetting”包含一个static方法“GetMenuItems”,该方法遍历配置文件并创建一个包含 3 个string(显示名称、视图名称和视图模型名称)的列表。

static public List<threesringholder> GetMenuItems()
{
	var list = new List<threesringholder>();
	var settings = new XmlReaderSettings();
	settings.XmlResolver = new XmlXapResolver();
	var reader = XmlReader.Create(configFile);
	reader.MoveToContent();
	while (reader.Read())
	{
		if (reader.NodeType == XmlNodeType.Element)
		{
			if (reader.Name == "MenuItem")
			{
				string d = reader.GetAttribute("Name");
				string v = reader.GetAttribute("View");
				string vm = reader.GetAttribute("ViewModel");
				var item = new ThreeSringHolder(d, v, vm);
				list.Add(item);
			}
			else
			{
				if (list.Count > 0)
				{
					break;
				}
			}
		}
	}
	return list;
}

GetAppSetting”中的类似代码从键值对中检索值,另一个覆盖方法允许在检索值时指定默认值。

侧边菜单

侧边菜单在WelcomePage文件中创建。它是一个ListBox(包含HyperlinkButton项),绑定到ModelViewRelation对象的集合。

private void CreateMenuList()
{
	MenuItemsList = new ObservableCollection<modelviewrelation>
			{
				new ModelViewRelation("Classic CD's", "ClassicView"),
			};
	var list = AppSetting.GetMenuItems();
	foreach (var item in list)
	{
		if ((string.IsNullOrEmpty(item.Name) == true) && 
			(string.IsNullOrEmpty(item.View) == true))
		{ //will create a menu separator line
			MenuItemsList.Add(new ModelViewRelation("", ""));
		}
		else if (string.IsNullOrEmpty(item.ViewModel) == true)
		{ //VM follows name convention

			MenuItemsList.Add(new ModelViewRelation(item.Name, item.View));
		}
		else
		{ //names for both V and VM are supplied

			MenuItemsList.Add(new ModelViewRelation
				(item.Name, item.View, item.ViewModel));
		}
	}
}

第一项是从代码中的值创建的;接下来,它调用“AppSetting.GetMenuItems”从配置文件中获取所有string。最后,它从列表中构造“ModelViewRelation”并将所有这些添加到“MenuItemsList”集合中。

此集合绑定到创建侧边菜单的ListBox,以便向用户显示显示名称。点击HyperlinkButton最终(利用 Silverlight 命令)会调用“MenuItemSelected”。

private void MenuItemSelected(object obj)
{
	var mvRelation = obj as ModelViewRelation;
	if (mvRelation != null)
	{
		CreateAndInject.CreateAndBindViewAndViewModel
				(mvRelation, InjectView, null);
	}
}

此方法创建并绑定 V-VM,并将其注入到添加到TabControlTabItem中。

基本视图模型及其接口

基本模型实现了IBaseViewModel接口,该接口支持绑定器进行的活动。

public interface IBaseViewModel : INotifyPropertyChanged
{
	object ModelData { get; set; }
	IDirectData DirectDataToView { get; set; }
	void LoadData();
	void Subscribe(IInforming informingView);
	void Subscribe(ILaunchChild launchingView);
}

它还实现了几个常用方法和属性,如InfoTextSubmitCommandPopUpView,以及处理视图到视图请求的NotifyEvent

Commands

CommandBase是一个简化创建命令处理程序的类。它允许我传递两个委托,一个用于 Can Execute,一个用于 Execute 本身。

摘要

通过小型的VvmScaffolding项目,可以获得一些基本视图-视图模型的好处。

它足够简单,我可以根据需要对其进行修改和扩展。我的下一步是尝试让命令在其他控件和其他事件(除了单击)上工作。MVVM 设计模式是一套建议,而不是一套规则……

历史

  • 初稿
© . All rights reserved.