在 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 模式,布布扣,bubuko.com

时间: 2024-08-05 23:41:29

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

angular中的MVVM模式

在开始介绍angular原理之前,我们有必要先了解下mvvm模式在angular中运用.虽然在angular社区一直将angular统称为前端MVC框架,同时angular团队也称它为MVW(Whatever)框架,但angular框架整体上更接近MVVM模式.下面是Igor Minar发布在Google+ https://plus.google.com/+IgorMinar/posts/DRUAkZmXjNV的文章内容: MVC vs MVVM vs MVP. What a controver

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

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

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

解决WPF程序中ListBox ItemsSource变化时不重置ScrollBar的问题 当我们改变ListBox的ItemsSource时,会发现这样一个问题:数据源变化时,虽然控件中的内容会跟着变化,但滚动条却不会重置. 举个例子: 将ListBox绑定到一百个字符串:listbox.ItemsSource = Enumerable.Range(0, 100).Select(i => "## " + i);. 将ListBox的滚动条拖到最后,使之能看到最后的"#

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

原文:WPF程序中App.Config文件的读与写 WPF程序中的App.Config文件是我们应用程序中经常使用的一种配置文件,System.Configuration.dll文件中提供了大量的读写的配置,所以它是一种高效的程序配置方式,那么今天我就这个部分来做一次系统性的总结. App.Config文件是系统默认的应用程序配置文件,在我们使用后进行编译时会生成"程序集名称+.exe.config"文件,其本质上也是一个XML文件,在我们的应用程序中添加应用程序配置文件后,默认生成下

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

原文:在 WPF 程序中应用 Windows 10 真?亚克力效果 从 Windows 10 (1803) 开始,Win32 应用也可以有 API 来实现原生的亚克力效果了.不过相比于 UWP 来说,可定制性会差很多. 本文介绍如何在 WPF 程序中应用 Windows 10 真?亚克力效果.(而不是一些流行的项目里面自己绘制的亚克力效果.) 本文内容 API 如何使用 注意事项 API 需要使用的 API 是微软的文档中并未公开的 SetWindowCompositionAttribute.

WPF中使用MVVM模式进行简单的数据绑定

计划慢慢整理自己在WPF学习和工作应用中的一些心得和想法,先从一个简单的用法说起 在WPF中,XAML标记语言中绑定数据,而数据源就是指定为ViewModel类,而非界面本身的逻辑代码类 这样一定程度上达到界面与业务逻辑分离的思想,UI层只需要对ViewModel类进行依赖,只要ViewModel公开出来的属性不变,界面层就不用根据业务逻辑变化而进行修改 这大概就是MVVM模式的一个基本出发点,配合WPF的依赖属性和命令等结合使用,会有更复杂的用法及更好的效果 这里先从一个简单的数据绑定用法为例

WPF中的MVVM模式简单实现

前言:在之前实现WPF程序时,我们可能会把所有的后台逻辑都放在视图的后台文件中,这样的实现方式的好处更直观,方便,对于一些小的应用程序这样做当然没什么问题,但是对于复杂的应用程序这样写的话,可能会导致后台代码显得非常臃肿,到最好变得难以维护.此时想到的解决方案就是职责分离,使后台的逻辑分离到其他类中,MVVM其实我理解就是达到这个目的.下面我们按照MVVM的组成部分来实现下这个MVVM程序. public class Person { public string Name { get; set;

WPF 从属性赋值到MVVM模式详解

示例源码 这两天学习了一下MVVM模式,和大家分享一下,也作为自己的学习笔记.这里不定义MVVM的概念,不用苍白的文字说它的好处,而是从简单的赋值讲起,一步步建立一个MVVM模式的Simple.通过前后对比留给读者自己去思考.我也不知道理解是否正确,有不对的地方,希望指出. 赋值VS绑定 要理解MVVM模式,最重要的是理解绑定的概念.做B/S或者对C/S理解不够的程序员可能不了解"绑定",它与赋值类似,但又"高级"一点. 一个简单的类: public class M

WPF学习笔记:MVVM模式下,ViewModel如何关闭View?

原文:http://blog.csdn.net/leftfist/article/details/32349731 矫枉过正,从一个极端走向另一个极端.MVVM模式,View只负责呈现,虽然也有后台代码,但基本上就是摆设,VM接管了一切的逻辑处理. 那么,现在,大能的VM已经完成了所有的事情,这个窗口V如何才能自动关闭呢? 据我目前少得可怜的WPF知识可知,有两种方案: 方案一.利用View里的IsEnable属性. 原理是这样的: 1.UI中的IsEnabled绑定VM中的属性 2.UI的后台