WCF 示例 – 第九章 – WPF 客户端 – 属性更改通知






4.88/5 (14投票s)
WPF INotifyPropertyChanged 实现。
![]() |
![]() |
|
第八章 | 第十章 |
系列文章
WCF 示例系列文章介绍了如何设计和开发一个 WPF 客户端,使用 WCF 进行通信,并使用 NHibernate 进行持久化。本系列简介描述了文章的范围,并讨论了高层次的架构解决方案。
章节概述
在上一章中,我们介绍了 Relay Command,并演示了使用 XAML 中绑定的视图如何轻松调用服务方法。我们还发现了一个问题,即当调用客户服务方法时,ViewModel 没有通知 View 模型中发生的更新。在本章中,我们将讨论一种利用模型更改通知 View 的模式。
本章的源代码可在CodePlex 变更集 93477 中找到。eDirectory 解决方案的最新代码可在CodePlex 中找到。
由于第七章中进行的 ServiceAdapter
和 CommandDispatch
重构,进行了以下更改:
INotifyPropertyChanged 接口
INotifyPropertyChanged
接口可用于 XAML DataContext
实例,以通知 XAML View 模型中的属性已更改。该接口非常简单;它只公开一个名为“PropertyChanged
”的事件,View 订阅此事件,以便在事件触发时收到通知。如果我们检查事件处理程序,我们会看到它需要一个对象(在本例中是 ViewModel)和已更改属性的名称,它作为字符串传递。
我们可以在每个 ViewModel 类中实现此接口,但更好的方法是提供一个包含此函数和通用实现的基类,以便可以轻松重用。
ViewModelBase
![]() |
有些人指出 ViewModel 不应该知道 View,如果严格遵循 MVVM 模式,这可能是正确的。将 ViewModel 与 View 隔离的主要驱动力是为了测试目的。您可能想看看其他人对此话题的看法: mtaboy 的帖子:为什么您的 ViewModel 中有其 View 的引用。stl7 的帖子:重新耦合 Views 和 ViewModels。 在 MVVM Light Toolkit 中,Messenger 组件的用途之一正是将 ViewModel 与 Views 解耦。它需要更多的基础设施,但解决了大多数耦合问题。 |
public CustomerViewModel()
{
CustomerServiceAdapter = new ServiceAdapter<ICustomerService>();
Refresh();
01 View = new CustomerView { DataContext = this };
View.ShowDialog();
}
当模型更改时,我们需要调用基类中的 RaisePropertyChanged
。网上有许多这种模式的实现示例,最常见的实现是以字符串参数的形式传递属性名称。但是,如果由于某些重构而重命名或删除了属性,我们需要记住更新触发事件的代码;这种方法可能不是最合适的,因此在我们的解决方案中,我们提供了一个使用 Lambda 表达式的重载方法,并在编译时避免了上述问题。
ViewModelBase
的实现如下:
public class ViewModelBase :INotifyPropertyChanged
{
01 public event PropertyChangedEventHandler PropertyChanged = delegate { return; };
02 protected void RaisePropertyChanged(string propertyName)
{
VerifyPropertyExists(propertyName);
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
03 protected void RaisePropertyChanged<T>(Expression<Func<T>> expression)
{
var memberExpression = expression.Body as MemberExpression;
if (memberExpression == null)
throw new ArgumentException("expression must be a property expression");
RaisePropertyChanged(memberExpression.Member.Name);
}
[Conditional("DEBUG")]
04 private void VerifyPropertyExists(string propertyName)
{
PropertyInfo currentProperty = GetType().GetProperty(propertyName);
string message = string.Format("Property Name \"{0}\" does not exist in {1}",
propertyName, GetType());
Debug.Assert(currentProperty != null, message);
}
}
INotifyPropertyChanged
的实现只需一行(第 01 行)。我们在这里使用了一个技巧,这样在通知事件订阅者时就不必检查事件处理程序是否为 null
。
第 02 行中的方法是 RaisePropertyChanged
模式的常见实现;一个辅助方法(第 04 行)用于通知属性是否存在。第 03 行中的方法是一种增强的方法,前面提到的,它消除了以字符串形式传递属性名称的需要。
实现
只需对我们的 CustomerViewModel
进行一个小小的更改,就可以使其与新的抽象类一起工作;只需两行代码,我们的应用程序就能运行。
public class CustomerViewModel
01 : ViewModelBase
{
...
private void Refresh()
{
var result = CustomerServiceAdapter.Execute(s => s.FindAll());
Model = new CustomerModel { NewCustomerOperation = new CustomerDto(),
CustomerList = result.Customers};
02 RaisePropertyChanged(() => Model);
}
}
第一个改变是 CustomerViewModel
现在继承自 ViewModelBase
(第 01 行),这很简单。另一个改变是当我们想要通知 View 模型已改变时;这发生在 Refresh
方法中调用 FindAll
方法时。第 02 行指示了如何实现这一点,使用 Lambda 表达式来指示 ViewModel 的哪个属性已更新,在本例中是 Model。这是 View 需要刷新的唯一事情。
如果我们现在执行客户端并像上一章那样输入一些客户详细信息,这次当按下“保存”按钮时,我们可以看到客户端是如何刷新的,并在网格中显示正确的数据。值得注意的是,在新客户控件之后应用程序的外观;Refresh
方法将 CustomerDto
的一个新空白实例分配给 Model.NewCustomerOperation
属性,这会自动重置输入的值,是不是很棒?
章节总结
关于 INotifyPropertyChanged
模式,没有什么可多讨论的了;它简单而巧妙,这是个好消息。至此,我们的应用程序已运行。这是我们系列文章中的一个重要里程碑,这意味着我们能够向客户展示我们的工作;我们已准备好以高效和富有成效的方式与他们合作;我们可以与关键用户安排研讨会,演示新功能。尝试说服他们使用该应用程序;正如我们所提到的,如果需要,他们可以从 U 盘执行该应用程序,再简单不过了。如果你幸运的话,他们可能会开始在你的工单系统中记录缺陷和增强功能。你可能会在这个阶段很忙,但请放心,当项目达到 UAT 阶段时,你不会遇到任何重大意外。
下一章对我们应用程序的设计至关重要,依赖注入将引入我们的解决方案。我们需要删除客户端中对服务器组件的引用。一旦我们涵盖了 DI,我们将准备好涵盖 NHibernate 和 WCF 的实现。但请记住,我们不需要这两个来有效地收集业务需求。