在 WPF 程序中使用 MVVM 模式
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 模式的更多相关文章
- 如何让WPF程序用上MVVM模式
https://msdn.microsoft.com/zh-cn/magazine/dd419663.aspx
- angular中的MVVM模式
在开始介绍angular原理之前,我们有必要先了解下mvvm模式在angular中运用.虽然在angular社区一直将angular统称为前端MVC框架,同时angular团队也称它为MVW(What ...
- 在WPF程序中使用摄像头兼谈如何使用AForge.NET控件(转)
前言: AForge.NET 是用C#写的一个关于计算机视觉和人工智能领域的框架,它包括图像处理.神经网络.遗传算法和机器学习等.在C#程序中使用摄像头,我习惯性使用AForge.NET提供的类库.本 ...
- WPF 程序中启动和关闭外部.exe程序
当需要在WPF程序启动时,启动另一外部程序(.exe程序)时,可以按照下面的例子来: C#后台代码如下: using System; using System.Collections.Generic; ...
- 如何在WPF程序中使用ArcGIS Engine的控件
原文 http://www.gisall.com/html/47/122747-4038.html WPF(Windows Presentation Foundation)是美国微软公司推出.NET ...
- WPF程序中App.Config文件的读与写
WPF程序中的App.Config文件是我们应用程序中经常使用的一种配置文件,System.Configuration.dll文件中提供了大量的读写的配置,所以它是一种高效的程序配置方式,那么今天我就 ...
- 如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来
原文:如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来 title: "如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来" publishDate: 2019-06 ...
- 在 WPF 程序中应用 Windows 10 真?亚克力效果
原文:在 WPF 程序中应用 Windows 10 真?亚克力效果 从 Windows 10 (1803) 开始,Win32 应用也可以有 API 来实现原生的亚克力效果了.不过相比于 UWP 来说, ...
- 解决WPF程序中ListBox ItemsSource变化时不重置ScrollBar的问题
解决WPF程序中ListBox ItemsSource变化时不重置ScrollBar的问题 当我们改变ListBox的ItemsSource时,会发现这样一个问题:数据源变化时,虽然控件中的内容会跟着 ...
随机推荐
- WCF学习心得------(三)配置服务
配置服务 配置服务概述 在设计和实现服务协定后,便可以进行服务的配置.在其中可以定义和自定义如何向客户段公开服务,包括指定可以找到服务的地址,服务用于发送和接受消息的传输和消息编码,以及服务需要的安全 ...
- phpinfo详解
php的很多信息都可以从phpinfo中获取,下面就详细了解下phpinfo的输出内容 1 php版本信息 第一行显示当前php版本 PHP Version 5.5.12 2 php.ini文件的位置 ...
- pgsql 9.4修改数据库只读
先进入psql 切换到目标数据库 \c mydb 对于老表 grant usage on schema public to $read_only_user; grant select on all t ...
- [svn] linux命令——svn分支创建、合并
一.创建分支 1,创建一个分支 svn copy svn://xx.com/repo/trunk svn://xx.com/repo/branches/TRY-something -m 'make b ...
- 一.OSI与TCP
一. TCP/IP的由来 OSI参考模型由来 计算机网络产生的最初阶段,每个计算机厂商都实现了自己的一套计算机网络体系结构;异构的网络之间无法进行通信.因此,ISO委员会推出了一种用于开放系统互联的网 ...
- Android APP高效开发的十大建议
在使用Android开发APP过程中,为什么确保最优化.运行流畅且不会使Android系统出现问题至关重要呢?因为影响APP产品效率的每一个问题,如:耗电或内存占用情况等,都是关乎APP成功与否关键因 ...
- OAF_MDS系列2_OAF页面的通过MDS多语言开发国际化(案例)
2014-06-06 Created By BaoXinjian
- 白书 4.1.2 模运算的世界 P291
1.逆元 这里有个注意事项要说,就是当要求 (a-b)%m 的时候要注意不能直接 (a%m-b%m)%m 原因是得出的值有可能是负数,所以 (a%m-b%m+m)%m 才是正确的. //x,y是引用 ...
- laravel5.2 移植到新服务器上除了“/”路由 ,其它路由对应的页面显示报404错误(Object not found!)———新装的LAMP没有加载Rewrite模块
Laravel 框架通过 public/.htaccess 文件来让网址不需要 index.php.如果你的服务器是使用 Apache,请确认是否有开启 mod_rewrite 模块.如果 Larav ...
- 【jQuery】关于选择器中的 :first 、 :first-child 、 :first-of-type
[:first] <!DOCTYPE html><html lang="zh-CN"><head> <title>test&l ...