65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (14投票s)

2010 年 9 月 19 日

CPOL

5分钟阅读

viewsIcon

45214

WPF INotifyPropertyChanged 实现。

Previous Next
第八章 第十章

系列文章

WCF 示例系列文章介绍了如何设计和开发一个 WPF 客户端,使用 WCF 进行通信,并使用 NHibernate 进行持久化。本系列简介描述了文章的范围,并讨论了高层次的架构解决方案。

章节概述

上一章中,我们介绍了 Relay Command,并演示了使用 XAML 中绑定的视图如何轻松调用服务方法。我们还发现了一个问题,即当调用客户服务方法时,ViewModel 没有通知 View 模型中发生的更新。在本章中,我们将讨论一种利用模型更改通知 View 的模式。

本章的源代码可在CodePlex 变更集 93477 中找到。eDirectory 解决方案的最新代码可在CodePlex 中找到。

由于第七章中进行的 ServiceAdapterCommandDispatch 重构,进行了以下更改:

INotifyPropertyChanged 接口

INotifyPropertyChanged 接口可用于 XAML DataContext 实例,以通知 XAML View 模型中的属性已更改。该接口非常简单;它只公开一个名为“PropertyChanged”的事件,View 订阅此事件,以便在事件触发时收到通知。如果我们检查事件处理程序,我们会看到它需要一个对象(在本例中是 ViewModel)和已更改属性的名称,它作为字符串传递。

我们可以在每个 ViewModel 类中实现此接口,但更好的方法是提供一个包含此函数和通用实现的基类,以便可以轻松重用。

ViewModelBase

ViewModelBase 抽象类实现了 INotifyPropertyChanged,它为 ViewModel 类提供了一个简单的机制来指示模型属性何时更改。ViewModel 在 ViewModel 构造函数中被分配为 View 的 DataContext,如下面的代码片段中的第 01 行所示。

有些人指出 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 的实现。但请记住,我们不需要这两个来有效地收集业务需求。

© . All rights reserved.