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

示例源码

这两天学习了一下MVVM模式,和大家分享一下,也作为自己的学习笔记.这里不定义MVVM的概念,不用苍白的文字说它的好处,而是从简单的赋值讲起,一步步建立一个MVVM模式的Simple.通过前后对比留给读者自己去思考.我也不知道理解是否正确,有不对的地方,希望指出.

赋值VS绑定

要理解MVVM模式,最重要的是理解绑定的概念.做B/S或者对C/S理解不够的程序员可能不了解"绑定",它与赋值类似,但又"高级"一点.

一个简单的类:

public class MyClass
{
    public MyClass() {
        this._Time = DateTime.Now.ToString();
    }     

    private string _Time;
    public string Time {
        get {
            return this._Time;
        }
        set {
            this._Time = value;
        }
    }
}

赋值

private void UpdateTime_Click(object sender, RoutedEventArgs e) {
    _MyClass.Time = DateTime.Now.ToString();
    this.lable1.Content = _MyClass.Time;
}

private void Grid_Loaded(object sender, RoutedEventArgs e) {
    this.lable1.Content = _MyClass.Time;
}

很简单的对lable1的Content属性的赋值.总结一下这种模式的流程图:

这种模式很简单,很容易理解.缺点也是很明显,View跟CodeBehind紧紧耦合在一起了(事件方法里面需要知道lable1),还有到处都是this.lable1.Content = _MyClass.Time; 这样的赋值代码,这样可维护性是很低的.于是就有了绑定.

属性绑定

绑定就是把把东西关联在一起,例如人的手脚是和整个身体绑定在一起的,手指受伤了,人会感到疼痛.属性绑定通常是把一个Model属性绑定给一个控件的属性,于是它们就有了联系,Model的属性变化了,控件的属性也会变化.

wpf的绑定.

首先把View的DataContext设为MyClass.

<Window.DataContext>
    <local:MyClass />
</Window.DataContext>

这样我们就可以把MyClass的属性绑定给lable1的Content.

<Label Grid.Column="1" Grid.Row="1" Content="{Binding Time}" />

WinForm也能绑定:

public Form1() {
    InitializeComponent();
    this.label2.DataBindings.Add("Text", _MyClass, "Time", true);
}

运行程序:

点击Update Time按钮,比较遗憾,绑定那一行的时间并没有更新.看来需要做更多的工作.(见源码Example1)

INotifyPropertyChanged接口

原来对于上面的那个poco类,它的属性Time发生变化时,紧紧靠<Label Grid.Column="1" Grid.Row="1" Content="{Binding Time}" />或者this.label2.DataBindings.Add("Text", _MyClass, "Time", true); 是不够的,lable不能"智能"地知道MyClass的Time变化了,需要MyClass主动去通知lable:我的Time属性变化了.INotifyPropertyChanged接口就是这样的功能.

INotifyPropertyChanged的源码:

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

PropertyChangedEventHandler里的事件参数源码:

// 摘要:为 System.ComponentModel.INotifyPropertyChanged.PropertyChanged 事件提供数据。
 public class PropertyChangedEventArgs : EventArgs
 {
     // 摘要:初始化 System.ComponentModel.PropertyChangedEventArgs 类的新实例。
     // 参数:propertyName:已更改的属性名
     [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
     public PropertyChangedEventArgs(string propertyName);

     // 摘要:获取已更改的属性名。
     // 返回结果:已更改的属性名。
     public virtual string PropertyName { get; }
 }

接口非常简单,就一个PropertyChanged事件,而事件委托的参数也很简单,一个字符串属性名.Model继承INotifyPropertyChanged后,在这个事件中是通知者的角色(执行事件),而<Label Grid.Column="1" Grid.Row="1" Content="{Binding Time}" />和this.label2.DataBindings.Add("Text", _MyClass, "Time", true); 这里可以理解为事件的订阅.

继承INotifyPropertyChanged后的MyClass:

public class MyClass : INotifyPropertyChanged
{
    public MyClass() {
        this._Time = DateTime.Now.ToString();
    }

    private string _Time;
    public string Time {
        get {
            return this._Time;
        }
        set {
            if (this._Time != value) {
                this._Time = value;
                if (PropertyChanged != null) {
                    PropertyChanged(this, new PropertyChangedEventArgs("Time"));
                }
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

重点是Set值时执行事件,运行程序发现,lable终于知道MyClass的属性变化了,它们绑定了.而且可以发现绑定是双向的,即控件的值更新,model的属性值也会更新,添加一个按钮显示model的属性值:

private void Show_Click(object sender, RoutedEventArgs e) {
    MessageBox.Show(_MyClass.Time);
}

这里做到了把Model的属性绑定给View的控件的属性中,下面看看集合的绑定.

集合绑定

跟上面一样,普通的集合控件们是不认的,要用特殊的集合,它就是ObservableCollection<T>,它继承了INotifyCollectionChanged和INotifyPropertyChanged.部分源码:

 [Serializable]
 public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
一个简单的类:
public class Employe
{
    public ObservableCollection<string> Employees { get; set; }

    public Employe() {
        Employees = new ObservableCollection<string>()
        {
            "肥猫", "大牛", "猪头"
        };
    }
}

把它绑定到一个ComboBox中:

<ComboBox Grid.Column="2" Grid.Row="0"  ItemsSource="{Binding Employees}" Width="50px"/>

另外做一个按钮添加来Employees

private void AddDepartment_Click(object sender, RoutedEventArgs e) {
    _MyClass.Employees.Add(this.textBox1.Text);
}

运行程序,添加一个Employee,发现ComboBox也更新了(见源码Example3).

命令绑定

还有一个绑定就是命令绑定.实际解决的是要把View完全解耦,不用再写控件事件,因为AddDepartment_Click这样的写法就会把View和CodeBehind的耦合在一起,跟上面属性赋值类似.

ICommand

// 摘要:定义一个命令
[TypeConverter("System.Windows.Input.CommandConverter, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
[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);
}

最主要需要实现的是Execute方法.即事件发生时要执行的方法.下面把Add Department的按钮事件去掉,改为绑定一个命令.实现这个命令首先要得到的是textbox上的值.要在命令里得到View控件的值,可以在model里新建一个属性值与这个控件绑定,因为绑定是双向的,所以属性值就是控件的值.根据上面的Employe类添加如下代码:

private string _NewEmployee;
public string NewEmployee {
    get {
        return this._NewEmployee;
    }
    set {
        if (this._NewEmployee != value) {
            this._NewEmployee = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("NewEmployee"));
        }
    }
}

每个命令要实现为一个单独的类,继承ICommand,这里用一个委托把添加部门的逻辑转移到Employe中:

public class AddEmployeeCommand : ICommand
{
    Action<object> _Execute;
    public AddEmployeeCommand(Action<object> execute) {
        _Execute = execute;
    }

    public bool CanExecute(object parameter) {
        return true;
    }

    public event EventHandler CanExecuteChanged {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter) {
        _Execute(parameter);
    }
}

Employe类再添加一个ICommand用作绑定:

private ICommand _AddEmployee;
public ICommand AddEmployee {
    get {
        if (_AddEmployee == null) {
            _AddEmployee = new AddEmployeeCommand((p) =>
            {
                Employees.Add(NewEmployee);
            });
        }
        return _AddEmployee;
    }
}
有了AddEmployee 我们就可以绑定到按钮中:
<Button Grid.Column="0" Grid.Row="0" Content="Add Department" Command="{Binding AddEmployee}" />

到这里,我们可以得到跟上面一样的功能,但成功把按钮事件改为了命令绑定.(见源码Example4)

完成上面所有工作,我们解决了一个问题,即View"后面"的模块(Code Behind也好,Model也好)完全没了view的影子,"后面"的模块不用管textbox还是Label来显示一个Name,只管把Name赋值就好了,也不用关心一个button还是一个picturebutton来点击,只管实现逻辑.但细心观察,代码还是有不少问题.

其中最主要的是为了实现上面的功能,污染了Employe这个类.Employe应该是常见的Model层中的一个类,它应该是一个poco类,职责是定义领域模型和模型的领域(业务)逻辑.为了实现绑定,添加了各种接口和与领域(业务)无关的属性,这就是对Model的污染.所以,当想实现绑定,而又不想污染model,就得引入新的一层--ViewModel,这样就走向了MVVM模式.

MVVM模式

VM是MVVM的核心.主要作用有两个.

1.提供属性和命令供View绑定

2.还要承担MVC模式中C(Controller)的职责,作为View和业务层的中间人.

模式实践.

把上面的代码稍为修改即可以改为MVVM模式.

Model,Employee回归Poco:

public class Employee
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }

    public void Add() {
        DataBase.AllEmployees.Add(this);
    }
}

ViewModel提供绑定的属性和命令:

public class EmployeeViewModel : INotifyPropertyChanged
   {
       public event PropertyChangedEventHandler PropertyChanged;

       /// <summary>
       /// 供?ComboBox绑ó定¨
       /// </summary>
       public ObservableCollection<Employee> Employees { get; set; }

       public EmployeeViewModel() {
           Employees = new ObservableCollection<Employee>(DataBase.AllEmployees);
       }

       #region 供?textbox 绑ó定¨
       private string _NewEmployeeName;
       public string NewEmployeeName {
           get {
               return this._NewEmployeeName;
           }
           set {
               if (this._NewEmployeeName != value) {
                   this._NewEmployeeName = value;
                   if (this.PropertyChanged != null) {
                       PropertyChanged(this, new PropertyChangedEventArgs("NewEmployeeName"));
                   }
               }
           }
       }

       private string _NewEmployeeEmail;
       public string NewEmployeeEmail {
           get {
               return this._NewEmployeeEmail;
           }
           set {
               if (this._NewEmployeeEmail != value) {
                   this._NewEmployeeEmail = value;
                   if (this.PropertyChanged != null) {
                       PropertyChanged(this, new PropertyChangedEventArgs("NewEmployeeEmail"));
                   }
               }
           }
       }

       private string _NewEmployeePhone;
       public string NewEmployeePhone {
           get {
               return this._NewEmployeePhone;
           }
           set {
               if (this._NewEmployeePhone != value) {
                   this._NewEmployeePhone = value;
                   if (this.PropertyChanged != null) {
                       PropertyChanged(this, new PropertyChangedEventArgs("NewEmployeePhone"));
                   }
               }
           }
       }
       #endregion

       public ICommand AddEmployee {
           get {
               return new RelayCommand(new Action(() =>
                           {
                               if (string.IsNullOrEmpty(NewEmployeeName)) {
                                   MessageBox.Show("姓名不能为空!");
                                   return;
                               }
                               var newEmployee = new Employee { Name = _NewEmployeeName, Email = _NewEmployeeEmail, Phone = _NewEmployeePhone };
                               newEmployee.Add();
                               Employees.Add(newEmployee);
                           }));
           }
       }
   }

代码的职责非常明确,提供5个属性(1个命令,4个普通属性)供View绑定.虽然简单,但却产生了一大堆代码,可能这就是MVVM框架出现的原因.不管怎样,一个简单的MVVM模式的Simple就完成了(参考代码Example5).

MVVM:

时间: 2024-10-14 06:35:18

WPF 从属性赋值到MVVM模式详解的相关文章

iOS开发中MVC、MVVM模式详解

iOS中的MVC(Model-View-Controller)将软件系统分为Model.View.Controller三部分 Model: 你的应用本质上是什么(但不是它的展示方式) Controller:你的Model怎样展示给用户(UI逻辑) View:用户看到的,被Controller操纵着的 Controller可以直接访问Model,也可以直接控制View. 但Model和View不能互相通信. View可以通过action-target的方式访问Controller,比如我们在Sto

Javascript 严格模式详解

Javascript 严格模式详解 作者: 阮一峰 日期: 2013年1月14日 一.概述 除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict mode).顾名思义,这种模式使得Javascript在更严格的条件下运行. 设立"严格模式"的目的,主要有以下几个: - 消除Javascript语法的一些不合理.不严谨之处,减少一些怪异行为; - 消除代码运行的一些不安全之处,保证代码运行的安全: - 提高编译器效率,增加运行速度

Javascript设计模式之装饰者模式详解篇

一.前言: 装饰者模式(Decorator Pattern):在不改变原类和继承的情况下动态扩展对象功能,通过包装一个对象来实现一个新的具有原对象相同接口的新的对象. 装饰者模式的特点:1. 在不改变原对象的原本结构的情况下进行功能添加.2. 装饰对象和原对象具有相同的接口,可以使客户以与原对象相同的方式使用装饰对象.3. 装饰对象中包含原对象的引用,即装饰对象是真正的原对象经过包装后的对象. 二.Javascript装饰者模式详解: 描述:装饰者模式中,可以在运行时动态添加附加功能到对象中.当

Extjs MVC开发模式详解

Extjs MVC开发模式详解 在JS的开发过程中,大规模的JS脚本难以组织和维护,这一直是困扰前端开发人员的头等问题.Extjs为了解决这种问题,在Extjs 4.x版本中引入了MVC开发模式,开始将一个JS(Extjs)应用程序分割成Model-View-Controller三层,为JS应用程序的如何组织代码指明了方向,同时使得大规模JS代码变得更加易于重用和维护:这就是Extjs MVC开发模式的初衷. 在官方给出的MVC例子中,我们可以看到一个简单的列表编辑功能,这篇文章就围绕这个功能进

[No000013F]WPF学习之X名称空间详解

原文:[No000013F]WPF学习之X名称空间详解 X名称空间里面的成员(如X:Name,X:Class)都是写给XAML编译器看的.用来引导XAML代码将XAML代码编译为CLR代码. 4.1X名称空间里面到底都有些什么? x名称空间映射的是:http://schemas.microsoft.com/winfx/2006/xaml,望文生义,它包含的类均与解析XAML语言相关,所以亦称之为“XAML名称空间”. 与C#语言一样,XAML也有自己的编译器.XAML语言被解析并编译,最终形成微

Spartan6系列之芯片配置模式详解

1.   配置概述 Spartan6系列FPGA通过把应用程序数据导入芯片内部存储器完成芯片的配置.Spart-6 FPGA可以自己从外部非易失性存储器导入编程数据,或者通过外界的微处理器.DSP等对其进行编程.对以上任何一种情况,都有串行配置和并行配置之分,串行配置可以减少芯片对引脚的要求,并行配置对8bit/16bit Flash或者微处理器来说更合适. 因为Xilinx的FPGA器件的配置数据存储在CMOS 配置锁存器内(CCL),因此Spartan6 FPGA器件上电后必须重新配置.Sp

java 代理模式详解

java 动态代理(JDK和cglib) 设计模式这东东每次看到就明白可过段时间又不能很流利的说出来,今天就用详细的比喻和实例来加深自己的理解(小弟水平不高有不对的地方希望大家能指出来). (1)代理这个词生活中有很多比如在街边卖手机卡.充公交地铁卡的小商店他们都起了代理的作用,java中的代理跟这些小店商的作用是一样的.再比如我想在淘宝上开个服装店但又没有货源怎么办,这时候我就要跟淘宝上某一卖家联系做他的代理.我跟我的商家都要卖衣服(就好比我们都继承了卖衣服的接口sellClothesInte

设计模式 - 代理模式(proxy pattern) 未使用代理模式 详解

代理模式(proxy pattern) 未使用代理模式 详解 本文地址: http://blog.csdn.net/caroline_wendy 部分代码参考: http://blog.csdn.net/caroline_wendy/article/details/37698747 如果需要监控(monitor)类的某些状态, 则需要编写一个监控类, 并同过监控类进行监控. 但仅仅局限于本地, 如果需要远程监控, 则需要使用代理模式(proxy pattern). 具体方法: 1. 类中需要提供

在 WPF 程序中使用 MVVM 模式

MVVM 模式是一个很久之前的技术了,最近因为一个项目的原因,需要使用 WPF 技术,所以,重新翻出来从前的一段程序,重温一下当年的技术. MVVM 模式 MVVM 实际上涉及三个部分,Model, View 和 ViewModel ,三者的关系如下图所示. 在三部分的关系中,视图显示的内容和操作完全依赖于 ViewModel. Model 是应用程序的核心,代表着最大.最重要的业务资产,因为它记录了所有复杂的业务实体.它们之间的关系以及它们的功能. Model 之上是 ViewModel.Vi