简单MVVM教程(改进版) - 第二章

ktei2008 2012-03-21 06:12:45
加精
第一章见这里
上一次,我只是开了个头而已,然而在这一章中,我们将看到一点实际的代码了。我构想了很久,怎样让新手能快速掌握我想要传达的知识,然后我得出一个结论:一定一定要简单化,并且要有看的见摸的着的代码实例。好吧,我们开始。

打开你的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类:

这一贴的字基本用光了,下一贴继续。
...全文
2493 110 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
110 条回复
切换为时间正序
请发表友善的回复…
发表回复
ktei2008 2014-01-16
  • 打赏
  • 举报
回复
引用 109 楼 qldsrx 的回复:
INotifyPropertyChanged这个接口能否深入下,我举个例子,代码不够完整,仅表达意思而已:
public class A:INotifyPropertyChanged
{
   public B b{get;set;}
}
public class B:INotifyPropertyChanged
{
   public int c{get;set;}
}
这个时候我实例化对象A,而发生改变的是对象B中的属性c,按照道理说,引发的应该是c这个属性,对于A来说是捕获不到的,那么如果要A能捕获到c这个属性的变化,需要在A类中将这个INotifyPropertyChanged冒泡处理吧,是不是引发属性名为"b.c"这样的写法呢?对于这样的多级处理有点含糊。
感谢你的留言。这个教程好像是很久以前我写的,自己都没什么印象了。写的并不好,因为那个时候只是接触了点毛皮就觉得什么都会了,然后就来发教程。现在看看都觉得惭愧,哈哈。
qldsrx 2014-01-16
  • 打赏
  • 举报
回复
INotifyPropertyChanged这个接口能否深入下,我举个例子,代码不够完整,仅表达意思而已:
public class A:INotifyPropertyChanged
{
   public B b{get;set;}
}
public class B:INotifyPropertyChanged
{
   public int c{get;set;}
}
这个时候我实例化对象A,而发生改变的是对象B中的属性c,按照道理说,引发的应该是c这个属性,对于A来说是捕获不到的,那么如果要A能捕获到c这个属性的变化,需要在A类中将这个INotifyPropertyChanged冒泡处理吧,是不是引发属性名为"b.c"这样的写法呢?对于这样的多级处理有点含糊。
  • 打赏
  • 举报
回复
第三章呢?呵呵,等着更新呢。
zhang2xiaogang 2012-04-10
  • 打赏
  • 举报
回复
有点深奥,不过很不错,慢慢学习
ixkixkix 2012-03-31
  • 打赏
  • 举报
回复
好的,我加qq
ktei2008 2012-03-30
  • 打赏
  • 举报
回复
[Quote=引用 102 楼 的回复:]

这些控制的代码,是放在哪一层写好呢?
[/Quote]

通常来讲,放在ViewModel里比较好。你的这些问题,我在这里回答太麻烦。你可以加我QQ:38968002,有时间的话我跟你QQ上说
ixkixkix 2012-03-29
  • 打赏
  • 举报
回复
这些控制的代码,是放在哪一层写好呢?
ixkixkix 2012-03-29
  • 打赏
  • 举报
回复
对于这些控制,以前我的写法就是直接写在界面后台代码里,
逻辑多就很难写,而且没办法重用,如果有多个地方要用,就得写多份,
如何把这些很好的套到框架里去
ixkixkix 2012-03-29
  • 打赏
  • 举报
回复
大哥,我问个很有意义的问题,我一直为这个问题烦恼了几年了,
举个例子,学生信息的编辑,
编辑的字段有:学号,姓名,所属班级,地址。
字段说明:
学号:整数,大于0
姓名:字符,长度=10
所属班级:整数,关联班级表
地址:字符,长度=100

对于这样一些属性的字段,
如果我设计一个学生信息编辑界面,要输入这些信息,
那如何自动绑定这些属性呢?
比如学号是用一个文本框来编辑,那文本框只能输入>0的整数,

这些逻辑控制,如何通过控制层去控制?

Journey_ZZ 2012-03-29
  • 打赏
  • 举报
回复
那这个周末就等你跟新了!
谁能写个MVVM模式的WpfBrowserApplication的简单Dome,功能越简单越好。这样看的明白。
网上找的都是WpfApplication的Dome啊-,-!新手真难,谢谢了!有些不知道怎么用怎么用语言总结的疑惑。
[Quote=引用 97 楼 的回复:]
引用 96 楼 的回复:

ktei,等你跟新好多天了……


Sorry... I think I can update this weekend.
[/Quote]
ixkixkix 2012-03-28
  • 打赏
  • 举报
回复
不过能实现就行了。多谢你的回复。你给的连接也很有用。
ixkixkix 2012-03-28
  • 打赏
  • 举报
回复
[Quote=引用 92 楼 ktei2008 的回复:]
引用 88 楼 ixkixkix 的回复:

大哥,我发现数据绑定有个问题,
比如绑定学生列表吧,无法支持动态绑定,
就是数据源DataTable有多少列,就显示多少列,

不知道这个问题怎么解决?
比如查询学生列表,
写SQL语句,查出:学号、姓名、班级、地址。
过几天,用户要求加一列,加一个 “所修课程”列,
如果按你的绑定模式,就得修改类,增加“所修课程”属性,然后再修改……
[/Quote]

呵呵,设计模式有一条规则:对修改封闭,对扩展开放。
增加一列,不应该属于修改,应该属于扩展吧。
码仔2020 2012-03-28
  • 打赏
  • 举报
回复
呵呵、lz太强了、这么多代码都贴出。
ktei2008 2012-03-28
  • 打赏
  • 举报
回复
[Quote=引用 96 楼 的回复:]

ktei,等你跟新好多天了……
[/Quote]

Sorry... I think I can update this weekend.
Journey_ZZ 2012-03-28
  • 打赏
  • 举报
回复
ktei,等你跟新好多天了……
luckdwj 2012-03-27
  • 打赏
  • 举报
回复
收藏学习
woshizhaoxuhui 2012-03-27
  • 打赏
  • 举报
回复
收藏学习
ktei2008 2012-03-27
  • 打赏
  • 举报
回复
[Quote=引用 88 楼 ixkixkix 的回复:]

大哥,我发现数据绑定有个问题,
比如绑定学生列表吧,无法支持动态绑定,
就是数据源DataTable有多少列,就显示多少列,

不知道这个问题怎么解决?
比如查询学生列表,
写SQL语句,查出:学号、姓名、班级、地址。
过几天,用户要求加一列,加一个 “所修课程”列,
如果按你的绑定模式,就得修改类,增加“所修课程”属性,然后再修改界面,显示它。

但如果采用动态绑定的话,有……
[/Quote]

你的要求是DataTable返回多少列,就显示出多少列是吧?这其实是显示层的工作。我猜你是想直接绑上DataTable,然后它有多少列,你的界面就自动显示出多少列。GridView据我所知没有AutoGenerateColumns这种功能,但是WPF Toolkit里的DataGrid控件有这个功能。

WPF Toolkit: http://wpf.codeplex.com/releases/view/40535

但是你应该先跟用户谈好他们的需求到底是什么样的,然后再开始开发。如果后来他们的需求有变,你可以通过打补丁的方式升级他们的程序。
ktei2008 2012-03-27
  • 打赏
  • 举报
回复
非常感谢大家的支持和鼓励。
我会尽快更新的。
ktei2008 2012-03-27
  • 打赏
  • 举报
回复
[Quote=引用 87 楼 wsimei 的回复:]

引用 5 楼 luosaimingjavaandc 的回复:

lz可以把这个demo上传,提供下载 ,支持lz



code:
http://download.csdn.net/detail/wsimei/4174941
[/Quote]

非常感谢你的源码,方便了很多人啊!

我没想到会有这么多人来学习,如果你们觉得对你们有点帮助的话,就是我往下写的最大动力。再次感谢!
加载更多回复(50)

111,092

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧