在上一章中,我们只是开了个头而已,然而在这一章中,我们将看到一点实际的代码了。我构想了很久,怎样让新手能快速掌握我想要传达的知识,然后我得出一个结论:一定一定要简单化,并且要有看的见摸的着的代码实例。好吧,我们开始。
打开你的VS2010,新建一个WPF项目,命名为MvvmTutorial即可。紧接着,在当前Solution添加4个文件夹,分别为:Infrastructure, Views, ViewModels, Models。然后,把App.xaml改成如下:
 <
 Application x:Class="MvvmTutorial.App"
 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
 
</Application >
把MainWindow.xaml,MainWindow.xaml.cs删掉。在Views下添加一个新的WPF Window,命名为ShellView。在App.xaml.cs中加入:
 protected override void OnStartup(StartupEventArgs e)
 
{
 
base.OnStartup(e);
 
var shell = new ShellView();
 
shell.Show();
 
}
 
都完成了吗?现在按F5,你应该可以看到你的程序正常运行,并且已经可以看到主窗体了。至此,我们只是做了一些准备工作。Shell其实就是壳的意思,每一个Windows程序都会有一个主窗体,也就是一个壳,我们在这个壳上面拼凑各种View来构成一个丰富的应用程序。
现在我们来看一下具体我们要做些什么。我说过,尽量简单化,所以我们这章的任务很简单:就是把一些联系人显示在主窗体里。我们的联系人很简单,只有两个属性:名字和电话号码。我个人喜欢从ViewModel这一层出发,但是许多朋友还是习惯从Model层写起,那么我们就先来研究一下Model,即实体。在第一章中有人问是否应该在Model里实现INotifyPropertyChanged 接口
,我个人认为这个没有特别的死的做法。有点人愿意把Model尽量简单化,也就是说只是一个数据的载体而已,而把所有与View打交道的事情全部推给ViewModel。然而对于我们这个简单的例子,我选择让Model也实现INotifyPropertyChanged接口,如此一来,当我们对Model做出修改后,View上就可以显示出变化了(不过在今天这一章里,我们暂时不讨论ViewModel/Model的修改)。现在,在Models下添加一个Contact类。
我们的Model,很简单,具体是这个样子的:
 using MvvmTutorial.Infrastructure;
 
namespace MvvmTutorial.Models
 
{
 
/// <summary >
 
/// Our Contact model, which stores data retrieved from data persistence layer
 
/// </summary >
 
public class Contact : ObservableObject
 
{
 
#region Fields
 
string _name;
 
string _phoneNumber;
 
#endregion // Fields
 
#region Properties
 
public string Name
 
{
 
get
 
{
 
return _name;
 
}
 
set
 
{
 
if (_name != value)
 
{
 
_name = value;
 
RaisePropertyChanged(() = > Name);
 
}
 
}
 
}
 
public string PhoneNumber
 
{
 
get
 
{
 
return _phoneNumber;
 
}
 
set
 
{
 
if (_phoneNumber != value)
 
{
 
_phoneNumber = value;
 
RaisePropertyChanged(() = > PhoneNumber);
 
}
 
}
 
}
 
#endregion // Properties
 
}
 
}
 
你们可能注意到了这个Contact实际上继承了ObservableObject。这是一个抽象的基类,由于我们将来会有很多类都要实现INotifyPropertyChanged接口,所以我在这里写了一个基类。在Infrastructure下添加一个ObservableObject:
 using System;
 
using System.ComponentModel;
 
using System.Linq.Expressions;
 
namespace MvvmTutorial.Infrastructure
 
{
 
public class ObservableObject : INotifyPropertyChanged
 
{
 
public event PropertyChangedEventHandler PropertyChanged;
 
public virtual void RaisePropertyChanged(string propertyName)
 
{
 
if (PropertyChanged != null)
 
{
 
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
 
}
 
}
 
public void RaisePropertyChanged<T >(Expression<Func<T > > propertyExpression)
 
{
 
var propertyName = PropertySupport.ExtractPropertyName(propertyExpression);
 
this.RaisePropertyChanged(propertyName);
 
}
 
}
 
}
 
好的,这里我要说一下,其实这个类不是我个人写的,而是“剽窃”了一些现有的开源代码,拼凑起来的。请注意PropertySupport,这是一个静态类,它其实来自于微软的Prism框架里一小段代码,我之所以这样做只是让大家看的更清楚这些MVVM的框架内部大体是怎么回事,其实都是大同小异的。请在Infrastructure下添加PropertySupport:
 using System;
 
using System.Linq.Expressions;
 
using System.Reflection;
 
namespace MvvmTutorial.Infrastructure
 
{
 
public static class PropertySupport
 
{
 
public static string ExtractPropertyName<T >(Expression<Func<T > > propertyExpression)
 
{
 
if (propertyExpression == null)
 
{
 
throw new ArgumentNullException("propertyExpression");
 
}
 
var memberExpression = propertyExpression.Body as MemberExpression;
 
if (memberExpression == null)
 
{
 
throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
 
}
 
var property = memberExpression.Member as PropertyInfo;
 
if (property == null)
 
{
 
throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
 
}
 
var getMethod = property.GetGetMethod(true);
 
if (getMethod.IsStatic)
 
{
 
throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
 
}
 
return memberExpression.Member.Name;
 
}
 
}
 
}
 
有的人可能会问“PropertySupport”到底是干嘛的?其实我们经常写这样的代码:RaisePropertyChanged("SomeProperty"),这个代码本身没有任何问题,但是我们传入的是一个string,这也就暗示了两个小问题:1.当你的Property改名字以后,你需要修改这个string;2.输入string是个稍微容易出错的过程(打字错误)。那么PropertySupport.ExtractPropertyName便在牺牲了一点点效率的前提下,通过指定一个Expression来获得其属性的名字。所以我们就可以写成:RaisePropertyChanged(() = > PhoneNumber);如此一来,你只需要指定哪个属性即可,至于它的名字,不需要你操心,而且当下次你修改PhoneNumber为PrivatePhoneNumber后,你也不需要修改任何string。
Okay,既然我们说了要显示出一系列联系人,我们便需要一个View和ViewModel。在ViewModels下添加ContactMasterViewModel:
 using System.Collections.Generic;
 
using System.Collections.ObjectModel;
 
using MvvmTutorial.Infrastructure;
 
using MvvmTutorial.Models;
 
namespace MvvmTutorial.ViewModels
 
{
 
public class ContactMasterViewModel : ObservableObject
 
{
 
#region Fields
 
private bool _isLoaded;
 
private ObservableCollection<Contact > _items = new ObservableCollection<Contact >();
 
#endregion // Fields
 
#region Properties
 
public IEnumerable<Contact > Items
 
{
 
get
 
{
 
// If we load this view model in design mode (for example, in VS or Expression),
 
// we add some random data so that we can preview the layout of our view
 
if (DesignHelper.IsInDesignMode)
 
{
 
for (int i = 0; i < 25; ++i)
 
{
 
_items.Add(new Contact().GenerateRandomData());
 
}
 
}
 
else if (!_isLoaded)
 
{
 
Load();
 
}
 
return _items;
 
}
 
}
 
#endregion // Properties
 
#region Private Methods
 
private void Load()
 
{
 
// TODO: Once we finish the implementation of data persistence layer,
 
// we need to re-write this code to make this method work for real-world app.
 
// We haven't implemented data persistence
 
// Therefore, we temporarily load some random data in memory
 
for (int i = 0; i < 25; ++i)
 
{
 
_items.Add(new Contact().GenerateRandomData());
 
}
 
_isLoaded = true;
 
}
 
#endregion // Private Methods
 
}
 
}
 
注意这里面的DesignHelper:在Infrastructure下添加DesignHelper类:
                










