MVVM 自制版本





5.00/5 (4投票s)
一个简单的代码,展示了如何创建、绑定和注入视图和视图模型
引言
MVVM 的一些方面让我觉得很贴切,我尝试看看构建最简单的 MVVM 需要什么。我从创建、绑定以及我需要的一些基本功能的概念开始。下面的代码很简单、可扩展,并且提供了一种更容易理解 MVVM 概念的方法。我的方法是:MVVM 设计模式是一套建议,而不是一套规则……
脚手架
该项目提供了 MVVM 模式的框架和构建块,以及其他一些基本工具。大部分的“功夫”都发生在“CreateAndInject”文件中,该文件包含一个同名的类。这个类公开了一个名为“CreateAndBindViewAndViewModel
”的static
方法,该方法实例化对象,绑定它们,最后将它们注入我想要的地方(详情请参阅接下来的 3 个部分)。
Scaffolding
项目还支持读取配置数据,它包含一些基本的接口、一些事件、委托和支持数据类,以及初始命令(基本命令,将在下一期中扩展)。
我将从视图-视图模型(V-VM)的定义开始,该定义包含在“ModelViewRelation
”类中(在同名文件中)。在这里,我指定了两个类型的名称(使用string
)和一个可能的显示名称。为了方便起见,我还允许此定义包含一个对象(在我尝试将数据注入 V-VM 时会很有用)。
VM 和 V 的创建是通过反射完成的,为此我需要(除了类型名称之外) V 和 VM 类型所在的程序集以及类型的命名空间。 “ModelViewRelation
”包含这些string
s 的默认值;这些值也可以在运行时通过编程方式加载,或在‘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 的“罪过”……),此方法检查声明了哪些接口,并基于此执行某些操作。按出现顺序:
- 使用
IBaseViewModel
接口的一部分ModelData
变量将数据注入 VM,该变量位于BaseViewModel
类中。仅当用户在ModelViewRelation
中设置可选参数‘Anything
’时,此操作才会发生(请参阅“BaseViewMode
”中的“InformingView_NotifyEvent
”方法的示例)。这对于编写通用 ViewModel 很有用。 - 将视图的引用传递给 VM 的
DirectDataToView
变量(我知道,字面意义上的 MVVM 信徒会摇头)。此犯罪/罪恶仅在视图实现IDirectData
接口时发生(信徒:不要声明此接口)。当视图公开此接口时,ViewModel
可以将List< IModelViewData>
推送到视图。但更重要的是,如果ViewModel
向上转型DirectDataToView
并获得视图的句柄。几乎没有需要使用此接口。 - 调用
ViewModel
内的Load Data
方法,允许 VM 在视图“瞥见”下一步将绑定的数据之前执行任何所需的工作。LoadData
是IBaseViewModel
接口的一部分,虚拟方法位于BaseViewModel
中,并且显然可以被任何ViewModel
覆盖。BaseViewModel
的构造函数调用其自己的Initialize
来执行LoadData
可以被调用之前的任何活动(例如,准备 WCF 代理)。 - 将视图绑定到
ViewModel
,前提是视图是FrameworkElement
。 - 注册
ViewModel
以接收NotifyEvent
类型的自定义事件。此订阅仅在视图实现IInforming
接口时发生。这不是 MVVM 标准,但它允许我快速进行一些测试,克服命令的缺乏,并且通常情况下,如果您不希望打开此通信通道,则不要实现该接口。 - 注册
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
,我将其作为委托传递给Inj
方法,并将宿主留为空(主要是因为宿主尚不存在)。ectView
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
。此集合绑定到TabControl
的ItemSource
;因此,一个新TabItem
被添加到TabControl
。这个TabItem
包含了注入的View
。接下来,注入器将TopTabPage
设置为新的TabItem
,TopTabPage
绑定到TabControl
的SelectedItem
,因此我刚刚添加的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.xaml,ViewModel
遵循命名约定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,并将其注入到添加到TabControl
的TabItem
中。
基本视图模型及其接口
基本模型实现了IBaseViewModel
接口,该接口支持绑定器进行的活动。
public interface IBaseViewModel : INotifyPropertyChanged
{
object ModelData { get; set; }
IDirectData DirectDataToView { get; set; }
void LoadData();
void Subscribe(IInforming informingView);
void Subscribe(ILaunchChild launchingView);
}
它还实现了几个常用方法和属性,如InfoText
、SubmitCommand
、PopUpView
,以及处理视图到视图请求的NotifyEvent
。
Commands
CommandBase
是一个简化创建命令处理程序的类。它允许我传递两个委托,一个用于 Can Execute,一个用于 Execute 本身。
摘要
通过小型的VvmScaffolding
项目,可以获得一些基本视图-视图模型的好处。
它足够简单,我可以根据需要对其进行修改和扩展。我的下一步是尝试让命令在其他控件和其他事件(除了单击)上工作。MVVM 设计模式是一套建议,而不是一套规则……
历史
- 初稿