到目前为止,我的模型实现了INotifyPropertyChanged
,并且每个属性都会引发此事件。
几乎所有ViewModel都通过PropertyChangedEventHandler
收听这些更改。
问题是,即使属性更改对于View不重要,也会为模型中的每个更改调用此处理程序。
一种选择是检查引发事件的属性。但是,我不喜欢测试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
}
}
这完全不安全,因为我的模型中属性的名称可以更改,我必须在不同的地方修复它。
答案 0 :(得分: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.
}
您可以看到如何使用它的示例here。
至于选择哪个选项 - 我相信执行时间方面的差异可以忽略不计,与编码开销和代码混乱(代码本身和智能感知)相比,你可以获得额外的事件每个属性。我肯定会选择第一个选项。
修改强>
不幸的是,在处理PropertyChanged
事件时,您只能对PropertyName
字符串进行测试,即使属性名称发生更改,也无法以保持一致的方式获取该字符串。对于依赖项属性,您有MyDependencyProperty.Name
,但这不适用于常规属性。
最终,您的选项要么为每个属性使用不同的事件,要么在定义包含属性名称的属性的类中定义一个常量,希望您在/如果更改时记得修改它属性名称。假设您没有多个实现INotifyPropertyChanged
的类,您自己附加了一个处理程序,那么在这些特定类中为每个属性设置一个事件并不是那么糟糕。
答案 1 :(得分: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
。至少这是一个开始:)
答案 2 :(得分: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))
这不理想但没有字符串很难实现。