MVVM 模式是一个很久之前的技术了,最近因为一个项目的原因,需要使用 WPF 技术,所以,重新翻出来从前的一段程序,重温一下当年的技术。

MVVM 模式

MVVM 实际上涉及三个部分,Model, View 和 ViewModel ,三者的关系如下图所示。

在三部分的关系中,视图显示的内容和操作完全依赖于 ViewModel。

Model 是应用程序的核心,代表着最大、最重要的业务资产,因为它记录了所有复杂的业务实体、它们之间的关系以及它们的功能。

Model 之上是 ViewModel。ViewModel 的两个主要目标分别是:使 Model 能够轻松被 WPF/XAML View 使用;将 Model 从 View 分离并对 Model 进行封装。这些目标当然非常好,但是由于一些现实的原因,有时并不能达到这些目标。

您构建的 ViewModel 知道用户在高层上将如何与应用程序交互。但是,ViewModel 对 View 一无所知,这是 MVVM 设计模式的重要部分。这使得交互设计师和图形设计师能够在 ViewModel 的基础上创建优美、有效的 UI,同时与开发人员密切配合,设计适当的 ViewModel 来支持其工作。此外,View 与 ViewModel 的分离还使得 ViewModel 更有利于单元测试和重用。

由于视图模型的变化要影响到视图的状态,我们需要使用两个重要的技术:可观察对象和命令模式。

可观察对象

可观察对象要求当对象的状态发生变化的时候,需要能够主动通知所有的观察者,在 WPF 中涉及到两个重要的接口 INotifyPropertyChanged 和 INotifyCollectionChanged,它们分别用来表示单个对象的状态发生了变化,和一个集合发生了变化。

INotifyPropertyChanged 接口的定义如下所示:

namespace System.ComponentModel
{
// 摘要:
// 向客户端发出某一属性值已更改的通知。
public interface INotifyPropertyChanged
{
// 摘要:
// 在更改属性值时发生。
event PropertyChangedEventHandler PropertyChanged;
}
}

这是一个接口,通常我们会定义一个实现这个接口的基类来便于使用。在下面的实现中,通过事件来通知所有的观察者。

namespace MVVM.Framework
{
// 实现观察者主题的通知
public class BaseObservableObject : INotifyPropertyChanged
{
// 事件
public event PropertyChangedEventHandler PropertyChanged; // 标准的触发事件的方法
protected void OnPropertyChanged(string propertyName)
{
// 如果没有注册,会是 null
if (PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
}
}

而 INotifyCollectionChanged 的定义如下,系统已经提供了一个泛型的实现 ObservableCollection,定义在命名空间 System.Collections.ObjectModel 中,我们可以直接使用。

namespace System.Collections.Specialized
{
// 摘要:
// 向侦听器通知动态更改,如在添加或移除项时或在刷新整个列表时。
[TypeForwardedFrom("WindowsBase, Version=3.0.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
public interface INotifyCollectionChanged
{
// 摘要:
// 当集合更改时发生。
event NotifyCollectionChangedEventHandler CollectionChanged;
}
}

命令模式

对于命令模式来说,最重要的就是我们将每个命令封装为一个对象,这里涉及的接口是 ICommand,定义如下:

namespace System.Windows.Input
{
// 摘要:
// 定义一个命令
[TypeConverter("System.Windows.Input.CommandConverter, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
[TypeForwardedFrom("PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
[ValueSerializer("System.Windows.Input.CommandValueSerializer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
public interface ICommand
{
// 摘要:
// 当出现影响是否应执行该命令的更改时发生。
event EventHandler CanExecuteChanged; // 摘要:
// 定义用于确定此命令是否可以在其当前状态下执行的方法。
//
// 参数:
// parameter:
// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 null。
//
// 返回结果:
// 如果可以执行此命令,则为 true;否则为 false。
bool CanExecute(object parameter);
//
// 摘要:
// 定义在调用此命令时调用的方法。
//
// 参数:
// parameter:
// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 null。
void Execute(object parameter);
}
}

命令中,不仅包含了执行的方法,还包含了用来判断是否可以执行的方法,以及当是否可以执行发生变化的事件,这使得我们可以在数据状态发生变化的时候,动态通知视图的显示也同时发生变化,比如,在没有数据的情况下,修改按钮是不可用的,当已经存在数据的情况下,修改按钮就进入可用状态等等。

通常我们会实现这个接口。我们自己来提供两个方法分别实现需要执行的操作和判断是否可以执行的条件。这里涉及到两个委托。

Predicate 委托表示一个返回 bool 值的方法,这是一个泛型委托,我们可以传递一个参数进来,作为判断的条件。

namespace System
{
// 摘要:
// 表示定义一组条件并确定指定对象是否符合这些条件的方法。
//
// 参数:
// obj:
// 要按照由此委托表示的方法中定义的条件进行比较的对象。
//
// 类型参数:
// T:
// 要比较的对象的类型。
//
// 返回结果:
// 如果 obj 符合由此委托表示的方法中定义的条件,则为 true;否则为 false。
public delegate bool Predicate<in T>(T obj);
}

而 Action 委托则表示一个没有返回值的方法。我们在 View 中进行操作的时候,通常需要改变的是 ViewModel 的状态,并不需要返回结果给 View。

namespace System
{
// 摘要:
// 封装一个方法,该方法只有一个参数并且不返回值。
//
// 参数:
// obj:
// 此委托封装的方法的参数。
//
// 类型参数:
// T:
// 此委托封装的方法的参数类型。
public delegate void Action<in T>(T obj);
}

这样,我们默认的命令实现就成为如下的形式,DelegaeCommand 构造函数接收两个方法,一个就是被封装的实际操作,一个用来判断是否可用的方法。

另外额外提供了一个 UpdateCanExecuteState 方法, 在每次执行处理方法之后,自动调用一下,更新是否可用的状态。

namespace MVVM.Framework
{
/// <summary>
/// 实现命令支持
/// </summary>
public class DelegateCommand : ICommand
{
// 是否可执行的条件
private readonly Predicate<Object> canExecuteMethod; // 实际执行的操作, 表示有一个对象作为参数的方法
private readonly Action<Object> executeActionMethod; // 构造函数
// 创建命令对象的时候,提供实际执行方法的委托
// 判断是否启用的委托
public DelegateCommand(
Predicate<Object> canExecute,
Action<object> executeAction
)
{
canExecuteMethod = canExecute;
executeActionMethod = executeAction;
} #region ICommand Members public event EventHandler CanExecuteChanged; // 检查是否可以执行
public bool CanExecute(object parameter)
{
var handlers = canExecuteMethod; if (handlers != null)
{
return handlers(parameter);
} return true;
} // 执行操作
public void Execute(object parameter)
{
// 检查是否提供了实际的方法委托
var handlers = executeActionMethod; if (handlers != null)
{
handlers(parameter);
} // 执行之后,更新是否可以执行的状态
UpdateCanExecuteState();
} #endregion public void UpdateCanExecuteState()
{
var handlers = CanExecuteChanged; if (handlers != null)
{
handlers(this, new EventArgs());
}
}
}
}

第一部分先到这里,后面我们继续进行。

在 WPF 程序中使用 MVVM 模式的更多相关文章

  1. 如何让WPF程序用上MVVM模式

    https://msdn.microsoft.com/zh-cn/magazine/dd419663.aspx

  2. angular中的MVVM模式

    在开始介绍angular原理之前,我们有必要先了解下mvvm模式在angular中运用.虽然在angular社区一直将angular统称为前端MVC框架,同时angular团队也称它为MVW(What ...

  3. 在WPF程序中使用摄像头兼谈如何使用AForge.NET控件(转)

    前言: AForge.NET 是用C#写的一个关于计算机视觉和人工智能领域的框架,它包括图像处理.神经网络.遗传算法和机器学习等.在C#程序中使用摄像头,我习惯性使用AForge.NET提供的类库.本 ...

  4. WPF 程序中启动和关闭外部.exe程序

    当需要在WPF程序启动时,启动另一外部程序(.exe程序)时,可以按照下面的例子来: C#后台代码如下: using System; using System.Collections.Generic; ...

  5. 如何在WPF程序中使用ArcGIS Engine的控件

    原文 http://www.gisall.com/html/47/122747-4038.html WPF(Windows Presentation Foundation)是美国微软公司推出.NET ...

  6. WPF程序中App.Config文件的读与写

    WPF程序中的App.Config文件是我们应用程序中经常使用的一种配置文件,System.Configuration.dll文件中提供了大量的读写的配置,所以它是一种高效的程序配置方式,那么今天我就 ...

  7. 如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来

    原文:如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来 title: "如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来" publishDate: 2019-06 ...

  8. 在 WPF 程序中应用 Windows 10 真?亚克力效果

    原文:在 WPF 程序中应用 Windows 10 真?亚克力效果 从 Windows 10 (1803) 开始,Win32 应用也可以有 API 来实现原生的亚克力效果了.不过相比于 UWP 来说, ...

  9. 解决WPF程序中ListBox ItemsSource变化时不重置ScrollBar的问题

    解决WPF程序中ListBox ItemsSource变化时不重置ScrollBar的问题 当我们改变ListBox的ItemsSource时,会发现这样一个问题:数据源变化时,虽然控件中的内容会跟着 ...

随机推荐

  1. [Hibernate] - one to many

    事实上one to many 和 many to one是一样的,这是一个相互的过程. hibernate.cfg.xml <?xml version="1.0" encod ...

  2. Bouncycastle中的RSA技术以及解决之道

    一个使用bouncycastle进行安全操作的实用类 2007-04-13 12:54 import java.io.*;import java.security.*;import java.secu ...

  3. (转)VS.NET2010水晶报表安装部署[VS2010]

    本文转载自:http://www.cnblogs.com/xiaofengfeng/p/3325793.html 欢迎C#高手加盟QQ群:9340166 水晶报表VS2010版IDE安装标准版SAP ...

  4. IntelliJ IDEA通过Spring配置连接MySQL数据库

    先从菜单View→Tool Windows→Database打开数据库工具窗口,如下图所示: 点击Database工具窗口左上角添加按钮"+",选择Import from sour ...

  5. bzoj1966: [Ahoi2005]VIRUS 病毒检测

    Description 科学家们在Samuel星球上的探险仍在继续.非常幸运的,在Samuel星球的南极附近,探险机器人发现了一个巨大的冰湖!机器人在这个冰湖中搜集到了许多RNA片段运回了实验基地.科 ...

  6. 已跳过 'cache' -- 节点处于冲突状态

    svn resolved ./cache ./cache 为冲突文件路径“cache”的冲突状态已解决

  7. 242. Valid Anagram

    Given two strings s and t, write a function to determine if t is an anagram of s. For example,s = &q ...

  8. ios8 ios7 tableview cell 分割线左对齐

    ios8中左对齐代码 //加入如下代码 -(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cel ...

  9. tarjan算法 POJ3177-Redundant Paths

    参考资料传送门 http://blog.csdn.net/lyy289065406/article/details/6762370 http://blog.csdn.net/lyy289065406/ ...

  10. DBA_Oracle LogMiner分析重做和归档日志(案例)

    2014-08-19 Created By BaoXinjian