3

到目前为止,我的模型实现INotifyPropertyChanged了 ,并且每个属性都会引发此事件。几乎所有 ViewModel 都通过PropertyChangedEventHandler.

问题是模型中的每次更改都会调用此处理程序,即使属性更改对视图并不重要。

一种选择是检查哪个属性引发了事件。但是,我不喜欢测试 PropertyName 字符串的想法。它需要对我已经在模型中避免的属性名称进行硬编码,例如PropertyChanged.Notify(()=> PropertyName)

我看到的第二个选项是为我的所有属性实现单个事件:

public event PropertyChangedEventHandler LayerChanged;
public event PropertyChangedEventHandler FieldChanged;
public event PropertyChangedEventHandler LinkDictionaryChanged;

……

最佳做法是什么?我更喜欢第二种选择。

编辑:我尝试更具体

我的模型类是这样工作的:

  public bool IsFeatureLayer
        {
            get { return _isFeatureLayer; }
            set { PropertyChanged.ChangeAndNotify(ref _isFeatureLayer, value, () => IsFeatureLayer);}
        }

或者

  PropertyChanged.Notify(() => LinkDictionary);

所以问题不在于如何使通知调用更安全,因为我已经使用扩展方法来执行此操作而无需属性的字符串名称。

问题是如何在不使用字符串的情况下找出谁调用了事件。

 void _MCDAExtensionPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if(e.PropertyName.Equals("LinkDictionary"){
              //event handling
           }
        }

这是完全不安全的,因为我的模型中的属性名称可以更改,我必须在不同的地方修复它。

4

3 回答 3

3

如果您的目标是 .NET 4.5,那么使用新属性实现INotifyPropertyChanged起来会更容易、更安全。CallerMemberName

简而言之,该CallerMemberName属性允许您获取调用成员的名称作为方法参数。这样,你可以有这样的东西:

private string name;
public string Name
{
    get { return name; }
    set { SetProperty(ref name, value); }
}

private void SetProperty<T>(ref T field, T value, [CallerMemberName] string callerMemberName = "")
{
    // callerMemberName = "Name" (the property that called it).

    // Set the field value and raise PropertyChanged event.
}

您可以在此处查看如何使用它的示例。

至于选择哪个选项 - 我认为执行时间方面的差异可以忽略不计,而不是因为每个属性都有一个额外的事件而导致的编码开销和代码混乱(无论是在代码本身还是在智能感知中)。我肯定会选择第一个选项。

编辑:

不幸的是,在处理PropertyChanged事件时,您只能针对PropertyName字符串进行测试,即使属性名称发生更改,也无法以保持一致的方式获取该字符串。对于您拥有的依赖项属性MyDependencyProperty.Name,但这不适用于常规属性。

最终,您的选择是为每个属性使用不同的事件,或者在定义包含属性名称的属性的类中定义一个常量,希望您在/如果更改属性名称时记得修改它。假设您没有很多实现INotifyPropertyChanged您自己附加处理程序的类,那么为这些特定类中的每个属性设置一个事件并不是那么糟糕。

于 2012-11-17T20:11:02.390 回答
2

如果我正确理解您的问题,您可以使用以下内容:

public static class PropertyChangedExtensions
{
    public static void RegisterPropertyHandler<T, TProperty>(this T obj, Expression<Func<T, TProperty>> propertyExpression, PropertyChangedEventHandler handlerDelegate)
        where T : class, INotifyPropertyChanged
    {
        if (obj == null) throw new ArgumentNullException("obj");

        var propertyName = GetPropertyName(propertyExpression);

        obj.PropertyChanged += (sender, args) =>
            {
                if (args.PropertyName == propertyName && handlerDelegate != null)
                    handlerDelegate(sender, args);
            };
    }

    public static void Notify<T>(this PropertyChangedEventHandler eventHandler, object sender, Expression<Func<T>> propertyExpression)
    {
        var handler = eventHandler;
        if (handler != null) handler(sender, new PropertyChangedEventArgs(GetPropertyName(propertyExpression)));
    }

    private static string GetPropertyName(LambdaExpression propertyExpression)
    {
        var memberExpression = propertyExpression.Body as MemberExpression;
        if (memberExpression == null)
        {
            var unaryExpression = propertyExpression.Body as UnaryExpression;
            if (unaryExpression == null) 
                throw new ArgumentException("Expression must be a UnaryExpression.", "propertyExpression");

            memberExpression = unaryExpression.Operand as MemberExpression;
        }

        if (memberExpression == null) 
            throw new ArgumentException("Expression must be a MemberExpression.", "propertyExpression");

        var propertyInfo = memberExpression.Member as PropertyInfo;
        if (propertyInfo == null) 
            throw new ArgumentException("Expression must be a Property.", "propertyExpression");

        return propertyInfo.Name;
    }
}

RegisterPropertyHandler方法允许您在不使用“魔术字符串”的情况下为特定属性注册处理程序。你像这样使用它:

public class PersonViewModel : INotifyPropertyChanged
{
    public PersonViewModel()
    {
        Address = new AddressViewModel();
        Address.RegisterPropertyHandler(a => a.ZipCode, ZipCodeChanged);
    }

    private AddressViewModel _address;

    public AddressViewModel Address
    {
        get { return _address; }
        set
        {
            _address = value;
            PropertyChanged.Notify(this, () => Address);
        }
    }

    private static void ZipCodeChanged(object sender, PropertyChangedEventArgs args)
    {
        // This will only be called when the 'ZipCode' property of 'Address' changes.
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public class AddressViewModel : INotifyPropertyChanged
{
    private string _zipCode;

    public string ZipCode
    {
        get
        {
            return _zipCode;
        }
        set
        {
            _zipCode = value;
            PropertyChanged.Notify(this, () => ZipCode);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

我看到你已经有一个Notify扩展方法,所以你只需要添加RegisterPropertyHandler. 至少这是一个开始:)

于 2012-11-17T22:44:52.133 回答
1

只是对您的项目扩展方法是这样的:

    public static string GetPropertyName<TObj,TRet>(this TObj obj, Expression<Func<TObj,TRet>> expression)
    {
        MemberExpression body = GetMemberExpression(expression);
        return body.Member.Name;
    }

这样,您将对属性名称、带有属性名称的字符串进行编译检查,但会牺牲很少的性能。有了这个,你可以调用:

PropertyChanged.Notify(this.GetPropetyName(t=>t.PropertyName))

这并不理想,但没有字符串很难做到这一点。

于 2012-11-17T18:59:55.760 回答