第十八章:MVVM(六)

简介:

简化ViewModel
INotifyPropertyChanged的典型实现为类定义的每个公共属性都有一个私有支持字段,例如:

double number;

它还有一个OnPropertyChanged方法,负责触发PropertyChanged事件:

protected void OnPropertyChanged(string propertyName)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

典型的属性定义如下所示:

public double Number
{
    set
    {
        if (number != value)
        {
            number = value;
            OnPropertyChanged("Number");
            // Do something with the new value.
        }
    }
    get
    {
        return number;
    }
} 

潜在问题涉及传递给OnPropertyChanged方法的文本字符串。 如果拼错它,您将不会收到任何类型的错误消息,但涉及该属性的绑定将不起作用。 此外,支持字段在此单个属性中出现三次。 如果您有几个类似的属性并通过复制和粘贴操作定义它们,则可以省略重新命名后备字段的三个外观之一,并且该错误可能很难追踪。
您可以使用C#5.0中引入的功能解决第一个问题。 CallerMemberNameAttribute类允许您使用调用方法或属性的名称替换可选方法参数。
您可以通过重新定义OnPropertyChanged方法来使用此功能。 通过为它赋值null并在其前面加上方括号中的CallerMemberName属性,使参数可选。 您还需要System.Runtime.CompilerServices的using指令:

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

现在,Number属性可以调用OnPropertyChanged方法,而不使用指示属性名称的参数。 该参数将自动设置为属性名称“Number”,因为这是对OnPropertyChanged的调用所在的位置:

public double Number
{
    set
    {
        if (number != value)
        {
            number = value;
            OnPropertyChanged();
            // Do something with the new value.
        }
    }
    get
    {
        return number;
    } 
} 

此方法避免了拼写错误的文本属性名称,并且还允许在程序开发期间更改属性名称,而无需担心还会更改文本字符串。 实际上,发明CallerMemberName属性的主要原因之一是简化实现INotifyPropertyChanged的类。
但是,仅当从值正在更改的属性调用OnPropertyChanged时,此方法才有效。 在早期的ColorViewModel中,除了对OnPropertyChanged的一个调用之外,在所有调用中仍然需要显式属性名称。
可以更进一步简化set访问器逻辑:您需要定义一个通用方法,可能名为SetProperty或类似的东西。 此SetProperty方法也使用CallerMemberName属性定义:

bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
    if (Object.Equals(storage, value))
        return false;
    storage = value;
    OnPropertyChanged(propertyName);
    return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

SetProperty的第一个参数是对支持字段的引用,第二个参数是设置为该属性的值。 SetProperty自动检查和设置支持字段。 请注意,它在调用OnPropertyChanged时显式包含propertyName参数。 (否则propertyName参数将成为字符串“SetProperty”!)如果属性已更改,则该方法返回true。 您可以使用此返回值来使用新值执行其他处理。
现在Number属性如下所示:

public double Number
{
    set
    {
        if (SetProperty(ref number, value))
        {
            // Do something with the new value.
        }
    }
    get
    {
        return number;
    }
} 

虽然SetProperty是一个通用方法,但C#编译器可以从参数中推断出类型。 如果您不需要对属性集访问器中的新值执行任何操作,则甚至可以将两个访问器减少为单行而不会模糊操作:

public double Number
{
    set { SetProperty(ref number, value); }
    get { return number; }
}

您可能希望这样简化以至于您希望将SetProperty和OnPropertyChanged方法放在它们自己的类中,并在创建自己的ViewModel时从该类派生。 这样一个名为ViewModelBase的类已经存在于Xamarin.FormsBook.Toolkit库中:

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Xamarin.FormsBook.Toolkit
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected bool SetProperty<T>(ref T storage, T value, 
                                      [CallerMemberName] string propertyName = null)
        {
            if (Object.Equals(storage, value))
                return false;
            storage = value;
            OnPropertyChanged(propertyName);
            return true;
        }
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

本章将在本章的其余两个示例中使用。

目录
相关文章
|
存储 SQL 前端开发
借一个项目谈Android应用软件架构,你还在套用MVP 或MVVM吗
借一个项目谈Android应用软件架构,你还在套用MVP 或MVVM吗
|
前端开发 Java 程序员
iOS开发 - 抛开表面看本质之iOS常用架构(MVC,MVP,MVVM)
iOS开发 - 抛开表面看本质之iOS常用架构(MVC,MVP,MVVM)
471 0
|
前端开发
[译] 实用的 MVVM 和 RxSwift
今天我们将使用 RxSwift 实现 MVVM 设计模式。对于那些刚接触 RxSwift 的人,我 在这里 专门做了一个部分来介绍。
1372 0
|
前端开发 JavaScript Android开发
|
前端开发 JavaScript Android开发
|
前端开发 JavaScript Android开发
|
前端开发 Android开发 Windows
|
前端开发 JavaScript Android开发
|
JavaScript 前端开发 Android开发
|
JavaScript 前端开发 Android开发