玩转INotifyPropertyChanged和ObservableCollection
转载: http://www.cnblogs.com/Jax/archive/2009/10/13/1582128.html
本文的代码都是基于WPF的,对于Silverlight,这些技术也同样适用。
(一)INotifyPropertyChanged的使用场合
先写一个最简单的数据绑定,每次点击Button后,TextBlock的值都会自增1。
效果图如下所示:
这里使用了MVVM模式,并把Click事件抽象为了Command。
观察上面的代码,注意到几个细节:
1. UserName和Age属性作为ViewModel的两个属性,因为Age递增是基于绑定实现的,所以ViewModel要实现INotifyPropertyChanged接口。
2. 我们只在Age上添加了OnPropertyChanged方法,它会根据Age属性的变化而自动更新XAML中绑定的值。
而对于UserName属性,由于它是始终不变的,所以没有添加OnPropertyChanged方法。
由此可见,OnPropertyChanged方法决定了后台数据的变化是否能影响到前台绑定的XAML。
此外,对于一次性绑定(以后不会再改变)的属性,不要添加OnPropertyChanged方法,因为该方法会增加额外的性能开销。
但是,在MVVM模式中,我们常常将UserName和Age属性抽象出来,作为一个实体类:
public class UserInfo
{
public string UserName { get; set; }
public int Age { get; set; }
}
与此同时,在ViewModel中添加一个UserInfo类型的属性:
public class RegisterUserViewModel : INotifyPropertyChanged
{
public UserInfo User { get; set; }
注意:此时XAML中的绑定相应要修改为:
<TextBlock Name="tbUserName" Text="{Binding User.UserName}" …. />
<TextBlock Name="tbAge" Text="{Binding User. Age}" …. />
这时候问题就来了。到底是UserInfo实现INotifyPropertyChanged接口呢,还是ViewModel实现INotifyPropertyChanged接口呢?
1.如果UserInfo实现INotifyPropertyChanged接口,,那么UserInfo实体类相应地修改为:
public class UserInfo : INotifyPropertyChanged
{
public string UserName { get; set; } private int age;
public int Age
{
get
{
return this.age;
}
set
{
if (this.age != value)
{
this.age = value;
OnPropertyChanged("Age");
}
}
} #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
} #endregion
}
而ViewModel相应地就简化了:
public class RegisterUserViewModel
{
public RegisterUserViewModel()
{
this.User = new UserInfo { UserName = "Baobao", Age = 27 };
ClickCommand = new DelegateCommand<object>(OnClick, arg => true);
} void OnClick(object obj)
{
this.User.Age += 1;
} public UserInfo User { get; set; } public RegisterUserView View { get; set; }
public ICommand ClickCommand { get; set; }
}
注意到,此时ViewModel不需要实现INotifyPropertyChanged接口。因为INotifyPropertyChanged监视的是UserInfo实体里面的Age属性,所以在OnClick方法中,只要修改User实体的Age属性就能通知XAML做出改变。
2.如果UserInfo不实现INotifyPropertyChanged接口,那么这个实体类就非常简单了:
这时,ViewModel就必须要实现INotifyPropertyChanged接口,而且要为User属性添加OnPropertyChanged方法,以下是部分代码(省略了INotifyPropertyChanged接口的实现):
public class RegisterUserViewModel : INotifyPropertyChanged
{
public RegisterUserViewModel()
{
this.User = new UserInfo { UserName = "Baobao", Age = 27 };
ClickCommand = new DelegateCommand<object>(OnClick, arg => true);
} void OnClick(object obj)
{
this.User.Age += 1;
} private UserInfo user;
public UserInfo User
{
get
{
return this.user;
}
set
{
if (this.user != value)
{
this.user = value;
OnPropertyChanged("User");
}
}
} public RegisterUserView View { get; set; } public ICommand ClickCommand { get; set; }
}
如果运行上述代码,你会发现XAML中的Age并不会自增1。问题应该出在OnClick方法上,我在下面一行添加了断点:
this.User.Age += 1;
我发现User的Age属性确实由27变成了28,但是并没有把改变结果通知XAML。
这是因为,INotifyPropertyChanged接口只监视UserInfo这个实体的地址是否发生了改变,而目前这个地址并没有变化,只是存储在UserInfo实体中的成员Age发生了改变,而Age的地址并不在INotifyPropertyChanged接口的监视之下,所以XAML中没有任何改变。
看来只有修改UserInfo这个实体的地址了,最好的方法就是重新实例化一个UserInfo实体:
void OnClick(object obj)
{
//this syntax below cannot run, so it is marked
//this.User.Age += 1; this.User = new UserInfo() { UserName = this.User.UserName, Age = this.User.Age + 1 };
}
比较两种实现方式,我个人倾向于第一种,因为它更自然,而第二种,由于要new一个新的UserInfo对象,所以要多牺牲一些性能。但是不管哪种实现方式,都不要ViewModel和UserInfo同时实现INotifyPropertyChanged接口,这是非常消耗性能的。
(二)ObservableCollection和List的使用场合
始终对ObservableCollection怀有恐惧,因为它太笼统了。
首先它是一个集合,但它是一个既实现了INotifyPropertyChanged接口又实现了INotifyCollectionChanged接口的集合,这是它与List<T>的不同之处。
使用ObservableCollection<T>注定要比List<T>消耗性能,所以我一直在探寻它们各自不同的使用场合。而且,当泛型参数T也实现了INotifyPropertyChanged接口时,就更加复杂了。
于是分为以下四种情况:
1.最简单是List<T>,其中T没有实现INotifyPropertyChanged接口,就是一个简单的实体。
做一个简单的示例,统计一个班级的计算机成绩。
由于List<T>中的T没有实现INotifyPropertyChanged接口,所以增加、删除、修改这3个Button都不会起作用。
着眼于“及格”这个CheckBox,勾选后就只显示分数不小于60的学生列表,取消勾选择显示全部学生。为此,在XAML中要把CheckBox的绑定方式设置为TwoWay(虽然CheckBox默认就是TwoWay,但还是建议显式指定):
<CheckBox Name="chkPass" IsChecked="{Binding Path=IsPassed, Mode=TwoWay}">及格</CheckBox>
在ViewModel中,关键是ModelPropertyChanged方法,在IsPassed属性变化的时候,会执行这个方法:
void ModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "IsPassed":
this.StudentList = this.IsPassed ? StudentService.RetrievePassedStudentList() : StudentService.RetrieveStudentList();
break;
default:
break;
}
}
看得出,根据IsPassed属性的不同,分别为StudentList分配不同的地址,所以在XAML中的DataGrid会跟着发生改变。
随着这里的ViewModel也实现了INotifyPropertyChanged接口,但这是为了IsPassed属性和StudentList属性而实现的,与Student实体类无关。
如果大家读过我之前介绍MVP模式的一系列文章,这个小例子应该很简单。
结论:List<T>(T未实现INotifyPropertyChanged接口),适用于一次性绑定,适用于重新实例化整个List集合的绑定(例如上文的StudentList)。
2.稍微复杂点,List<T>,其中T实现了INotifyPropertyChanged接口。
也就是说,让Student实体类实现INotifyPropertyChanged接口。
public class Student : INotifyPropertyChanged
{
public int UserId { get; set; }
public string UserName { get; set; } private int score;
public int Score
{
get
{
return this.score;
}
set
{
if (this.score != value)
{
this.score = value;
OnPropertyChanged("Score");
}
}
} INotifyPropertyChanged Members
}
其它代码保持不变。
我们看到,Modify按钮可以使用了。修改后的数据立刻展现在XAML中。
但是,Add和Remove按钮仍然不能使用。因为INotifyPropertyChanged接口只是监视Student,而没有监视List<Student>集合。所以修改单笔Student数据,可以发送通知给XAML;但是修改List<Student>集合,除非像上一种方式那样重新实例化List<Student>集合(勾选CheckBox仅显示及格的学生),否则必须另辟蹊径。
结论:List<T>(T实现INotifyPropertyChanged接口),适用于修改集合中单笔数据的成员(比如说Student的Score属性)。
3.换个思路,用ObservableCollection<T>怎么样?其中T没有实现INotifyPropertyChanged接口。
也就是说,Student类仍然是一个简单的实体类:
public class Student
{
public int UserId { get; set; }
public string UserName { get; set; }
public int Score { get; set; }
}
相应地,把ViewModel中的List<Student>全都修改为ObservableCollection<Student>,并在构造函数中把原始的List集合转换为ObservableCollection集合:
public class ScoreListViewModel : INotifyPropertyChanged
{
public ScoreListViewModel()
{
this.StudentList = new ObservableCollection<Student>(StudentService.RetrieveStudentList()); AddCommand = new DelegateCommand<object>(OnAdd, arg => true);
ModifyCommand = new DelegateCommand<object>(OnModify, arg => true);
RemoveCommand = new DelegateCommand<object>(OnRemove, arg => true); this.PropertyChanged += ModelPropertyChanged;
} void ModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "IsPassed":
this.StudentList = new ObservableCollection<Student>(
this.IsPassed ? StudentService.RetrievePassedStudentList() : StudentService.RetrieveStudentList()); break;
default:
break;
}
} public ScoreListView View { get; set; } private bool isPassed;
public bool IsPassed
{
get
{
return this.isPassed;
}
set
{
if (this.isPassed != value)
{
this.isPassed = value;
OnPropertyChanged("IsPassed");
}
}
} public ObservableCollection<Student> StudentList { get; set; } public ICommand AddCommand { get; set; }
public ICommand ModifyCommand { get; set; }
public ICommand RemoveCommand { get; set; } public void OnAdd(object obj)
{
this.StudentList.Add(new Student() { UserId = 7, UserName = "李永京", Score = 75 });
} public void OnModify(object obj)
{
var item = this.StudentList.SingleOrDefault(x => x.UserId == 1);
if (item != null)
{
item.Score = 0;
//item = new Student() { UserId = item.UserId, UserName = item.UserName, Score = 0 };
}
} public void OnRemove(object obj)
{
var item = this.StudentList.SingleOrDefault(x => x.UserId == 2);
if (item != null)
{
this.StudentList.Remove(item);
}
} INotifyPropertyChanged Members
}
看一下点击Add和Remove按钮后的效果图:
而由于ObservableCollection<T>中的T没有实现INotifyPropertyChanged接口,所以修改功能又不能使用了。
这时,我尝试着将
item.Score = 0;
修改为:
item = new Student() { UserId = item.UserId, UserName = item.UserName, Score = 0 };
但仍然无济于事。别灰心,还有最后一种解决方案没试呢~
结论:ObservableCollection<T>(T未实现INotifyPropertyChanged接口),适用于增删集合中的数据(上面的Add和Remove按钮)。但是对于单笔数据的更改,是无能为力的。
4.最复杂的情况,就是使用ObservableCollection<T>,并且T实现了INotifyPropertyChanged接口。
代码结合了上述第2、第3种编程的方式:
结论:所有的问题在次全都迎刃而解。这也许是最完美的解决方案了。但是,也是最消耗性能的。
(三)数据绑定中的OneWay和TwoWay
OneWay和TwoWay是最常见的两种绑定方式,描述了源数据(控件)和目标数据(控件)之间的关系。现在,让我们基于MVP模式的思想,重新认识数据绑定机制。
在下面的概念中,源数据(控件),在MVP里变成了数据(Model);目标(数据)控件,则相应地变成了XAML中绑定的控件。
在WPF和Silverlight的数据绑定中,因控件的不同,其默认的绑定方向也不一样。
在Silverlight 3.0中,BindingMode枚举有3个值,默认是OneWay:
public enum BindingMode
{
OneWay = 1,
OneTime = 2,
TwoWay = 3
}
OneWay和OneTime是数据(Model)发生变化时通知XAML中的控件。而TwoWay则是数据和XAML之间任何一方发生变化都会通知对方。
我们在前面的例子中已经展示过,勾选“及格”这个CheckBox后,GridView中的数据会只显示Score>=60的学生,这个就是TwoWay的最好例证。
TwoWay,它适用于那些可以和用户互动的控件,比如说CheckBox和RadioButton的选中与否,比如在TextBox中输入了那些值,比如说在ListBox、ComboBox和GridView中选中了哪一行。我会在《Prism研究 之 包氏波动理念》来详细介绍其实现技术。
对于OneWay和OneTime,需要仔细说清楚。
A) OneWay是我们使用最频繁的了,无需多说,数据上的任何变化都会通知XAML中绑定的控件。
B) OneTime仅仅是一次性绑定。之后即使数据(Model)再发生变化,XAML中的控件也不会跟着改变。
还记得我们在本文的第一节《INotifyPropertyChanged的使用场合》举出的那个例子么?其中的UserName字段,就设置为OneTime的绑定方式,因为我们在初始化将其设置为”Baobao”后,就再也没改动过它。
将不变化的字段尽可能地设置为OneTime,而不是OneWay,这不就多少又节约了一些性能嘛。
而在WPF中,BindingMode枚举有5个值,默认是Default:
public enum BindingMode
{
TwoWay = 0,
OneWay = 1,
OneTime = 2,
OneWayToSource = 3,
Default = 4
}
OneWayToSource,从字面上很容易理解,它表示当XAML中的控件发生变化时,通知数据(Model),但是在反方向上,数据发生变化却不会通知XAML中的控件。它和OneWay是正好相反的。这样,OneWayToSource在MVP模式中就没有半点用武之处(它的最大用处其实在于把非依赖属性绑定到依赖属性上,参加http://book.51cto.com/art/200908/145469.htm)。
在WPF中,绑定的默认值是Default,这不同于Silverlight 3的默认绑定OneWay。Default表示它会根据控件的不同而未控件自动选择其绑定行为。比如对于TextBox、CheckBox、RadioBtton这些用户可编辑的控件,Default值就是TwoWay;而对于只是用来显示数据的控件(比如ListBox、Label),Default值就是OneWay。
玩转INotifyPropertyChanged和ObservableCollection的更多相关文章
- WPF ObservableCollection 异步调用问题
问题介绍 当ObservableCollection列表被UI线程占用时,如果在异步线程中调用ObservableCollection,会弹出以下异常: 问题分析 我们使用一个viewModel,在V ...
- WPF 中双向绑定通知机制之ObservableCollection使用
msdn中 ObservableCollection<T> 类 表示一个动态数据集合,在添加项.移除项或刷新整个列表时,此集合将提供通知. 在许多情况下,所使用的数据是对象的集合 ...
- 【UWP】FlipView绑定ItemsSource,Selectedindex的问题
最近在做列表头部的Carousel展示,Carousel使用的是FlipView展示,另外使用ListBox显示当前页,如下图 我们先设置一个绑定的数据源 public class GlobalRes ...
- WPF 面试题及答案(二)
一 · WPF中什么是样式? 首先明白WPF中样式属于资源中重要的一种. 同时样式也是属性值的集合,能被应用到一个合适的元素中,或者说能将一组属性应用到多个元素. WPF中样式可以设置任何依赖属性. ...
- wpMVVM模式绑定集合的应用
一.新建一个项目,命名为wpMVVMone,添加一个有关食品信息的类Food.CS,代码如下: public class Food { public string Name { get; set; } ...
- WindowsPhone8 数据库增删改查
今天第一次在博客园发表文章,如果有的地方写的不对,还请大家指出! 1.这就是一个简单wp8数据库增删改查 1.创建数据表Person [Table] public class Person : INo ...
- WP之Sql Server CE数据库
如何在WP8中进行数据存储,你首先想到应该是独立存储,但是独立存储似乎存储文件更方便,如果我们希望像处理对象的形式,该怎么办呢,答案就是Sql Server CE. Sql Server CE并不是新 ...
- Windows Phone 8 SQL Server CE 数据库
员工信息表 EmployeeTable.cs using System.Data.Linq.Mapping; using System.ComponentModel; namespace SQLSer ...
- 9宫拼图小游戏(WPF MVVM实现)
昨天逛论坛,看到一个哥们用WPF做了一个9宫的拼图游戏,发现初学WPF的人都很容易犯一个错误(我也犯过):把WPF当WINFORM用!所以想写一个比较符合WPF风格的版本,于是就抽工作的空余时间做了一 ...
随机推荐
- 构建具有用户身份认证的 React + Flux 应用程序
原文:Build a React + Flux App with User Authentication 译者:nzbin 译者的话:这是一篇内容详实的 React + Flux 教程,文章主要介绍了 ...
- 【★】RSA-什么是不对称加密算法?
不对称加密算法RSA浅析 本文主要介绍不对称加密算法中最精炼的RSA算法.我们先说结论,也就是RSA算法怎么算,然后再讲为什么. 随便选取两个不同的大素数p和q,N=p*q,r=(p-1)*(q-1) ...
- Beta阶段冲刺日志集合贴
[Beta]Daily Scrum Meeting--Day1:http://www.cnblogs.com/RunningGuys/p/6890738.html [Beta]Daily Scrum ...
- 团队作业——Alpha冲刺之事后诸葛亮
小组成员: 武健男:201421123091 林俊鹏:201421123076 何跃斌:201421123082 陈鑫龙:201421123078 潘益靖:201421123086 黄睿:201421 ...
- 201521123065《java程序设计》第七周学习总结
1. 本周学习总结 1.Iterator迭代器用于遍历集合中的元素: 2.使用迭代器删除元素一定要先指向下一个元素在删除第一个元素: 3.List可以有重复对象: Set不能有重复对象: 4.Map是 ...
- Java课设(学生信息管理系统)
1.团队课程设计博客链接 http://www.cnblogs.com/Min21/p/7064093.html 2.个人负责模板或任务说明 设计登陆界面和学生信息界面的设计,学生信息的显示.退出等功 ...
- 201521123096《Java程序设计》第十周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 2. 书面作业 本次PTA作业题集异常.多线程 1.finally 题目4-2 1.1 截图你的提交结果(出 ...
- SpringMVC第二篇【过滤编码器、注解开发、requestMapping、业务方法与传统参数】
SpringMVC过滤编码器 在SpringMVC的控制器中,如果没有对编码进行任何的操作,那么获取到的中文数据是乱码! 即使我们在handle()方法中,使用request对象设置编码也不行!原因也 ...
- Apache POI
Apache POI 用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能.POI为"Po ...
- 记一次 node.js 的populate用法
最近在学习node.js做一个简单的博客的网站,用的express框架和mongodb的数据库.之前没有接触过这个数据库,所有在一开始写的时候遇到了一些问题,如何初始化model类型,又如何实现简单的 ...