避免在每个setter中调用RaisePropertyChanged
我想摆脱模型类中占用空间和重复性的RaisePropertyChanged-Properties。 我想要我的模特class……
public class ProductWorkItem : NotificationObject { private string name; public string Name { get { return name; } set { if (value == name) return; name = value; RaisePropertyChanged(() => Name); } } private string description; public string Description { get { return description; } set { if (value == description) return; description = value; RaisePropertyChanged(() => Description); } } private string brand; public string Brand { get { return brand; } set { if (value == brand) return; brand = value; RaisePropertyChanged(() => Brand); } } }
…看起来像这样简单:(但是当属性发生变化时通知视图)
public class ProductWorkItem { public string Name{ get; set; } public string Description{ get; set; } public string Brand{ get; set; } }
这可以通过某种代理类来实现吗?
我想避免为每个模型类编写代理。
我知道在“vanilla”C#中没有简单和可维护的方法,但你可以用方面实现这一点。 我已经使用了PostSharp ,它的缺点是作为付费的第三方产品,但有一个免费版本,你也可以这样做。 PostSharp利用目标指定,inheritance等属性的优点,并将它们扩展到方面。
然后,您可以定义LocationInterceptionAspect
,它会覆盖OnSetValue
方法以调用您的RaisePropertyChanged
委托。 然后,您可以使用使用aspect属性修饰的自动生成的属性。
PostSharp的付费版本允许您在类级别执行此操作,因此您只需要一个属性(如果您装饰基类并将属性定义为可inheritance,则只需要一个属性)。 这在PostSharp站点上描述为InstanceLevelAspect
的用例
我来到NotifyPropertyWeaver扩展程序并从那时起定期使用它。 它是一个Visual Studio扩展,在编译代码之前为您实现始终相同的INPC内容。 你什么都没注意到。
您需要安装扩展,然后您的模型需要如下所示:
public class ProductWorkItem : INotifyPropertyChanged { public string Name{ get; set; } public string Description{ get; set; } public string Brand{ get; set; } public event PropertyChangedEventHandler PropertyChanged; }
扩展比为你添加所有其余的。 我喜欢这种方法的是,你的类仍然“正式”实现INPC接口,你也可以在非WPF上下文中使用它(因为INPC根本不是WPF的东西),但仍然没有用你所有的东西乱丢你的课。 它会针对依赖于属性的只读属性引发通知。
当然,它有点假,因为它只是自动化写作,并没有改变任何有关底层概念的东西。 但也许这是妥协……
以下是更多信息: 链接
我们可以避免在WPF中的每个属性设置器上编写RaisePropertyChanged的重复代码。
使用Postsharp的免费版本。
通过使用以下代码,我们只能将Virtual属性绑定到view。
namespace Test { [Serializable] [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] public sealed class RaisePropertyChangedAttribute : MethodInterceptionAspect { private string propertyName; /// /// Compiles the time validate. /// /// The method. public override bool CompileTimeValidate(MethodBase method) { return IsPropertySetter(method) && !method.IsAbstract && IsVirtualProperty(method); } /// /// Method invoked at build time to initialize the instance fields of the current aspect. This method is invoked /// before any other build-time method. /// /// Method to which the current aspect is applied /// Reserved for future usage. public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo) { base.CompileTimeInitialize(method, aspectInfo); propertyName = GetPropertyName(method); } /// /// Determines whether [is virtual property] [the specified method]. /// /// The method. /// /// true if [is virtual property] [the specified method]; otherwise, false . /// private static bool IsVirtualProperty(MethodBase method) { if (method.IsVirtual) { return true; } var getMethodName = method.Name.Replace("set_", "get_"); var getMethod = method.DeclaringType.GetMethod(getMethodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); return getMethod != null && getMethod.IsVirtual; } private static string GetPropertyName(MethodBase method) { return method.Name.Replace("set_", string.Empty); } /// /// Determines whether [is property setter] [the specified method]. /// /// The method. /// /// true if [is property setter] [the specified method]; otherwise, false . /// private static bool IsPropertySetter(MethodBase method) { return method.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase); } /// /// Method invoked instead of the method to which the aspect has been applied. /// /// Advice arguments. public override void OnInvoke(MethodInterceptionArgs args) { var arg = args as MethodInterceptionArgsImpl; if ((arg != null) && (arg.TypedBinding == null)) { return; } // Note ViewModelBase is base class for ViewModel var target = args.Instance as ViewModelBase; args.Proceed(); if (target != null) { target.OnPropertyChanged(propertyName); } } } }
我在System.Dynamic
命名空间中找到了这个类 …它允许您拦截绑定目标上的DependencyObject
对绑定源上的Property
进行的实际DataBinding
调用。
http://i.msdn.microsoft.com/en-us/library/system.windows.data.binding.DataBindingMostBasic(v=vs.110).png?appId=Dev11IDEF1&l=EN-US&k=k(System.Windows .Data.Binding)%3BK(VS.XamlEditor)%3BK(TargetFrameworkMoniker-.NETFramework
所以现在可以做的是实现一个实现INotifyPropertyChanged
的类(让我们称之为DynamicNpcProxy
),它派生自DynamicObject
并覆盖TryGetMember
和TrySetMember
方法。
public class DynamicNpcProxy : DynamicObject, INotifyPropertyChanged { public DynamicNpcProxy(object proxiedObject) { ProxiedObject = proxiedObject; } //... public object ProxiedObject { get; set; } public override bool TrySetMember(SetMemberBinder binder, object value) { SetMember(binder.Name, value); return true; } protected virtual void SetMember(string propertyName, object value) { GetPropertyInfo(propertyName).SetValue(ProxiedObject, value, null); if (PropertyChanged != null) PropertyChanged(ProxiedObject, new PropertyChangedEventArgs(propertyName)); } protected PropertyInfo GetPropertyInfo(string propertyName) { return ProxiedObject.GetType().GetProperty(propertyName); } // override bool TryGetMember(...) }
要使其工作,请将代理程序包装在当前绑定源周围,替换它们并让DynamicObject
完成剩下的工作。
在ViewModel.cs中:
IList items; //... assign items var proxies = items.Select(p => new DynamicNpcProxy(p)).ToList(); ICollectionView Products = CollectionViewSource.GetDefaultView(proxies);
在View.xaml中:
你最终得到的是:
还可以在code project
查看这篇文章 ,它提供了更多信息……
来自另一方(如果你没有花哨的扩展)你可以使用我的答案中概述的扩展方法“自动指定”已更改的属性: WCF服务代理不设置“FieldSpecified”属性
具体来说,您可以使用“非reflection方法”为每个类配置特定的“OnPropertyChanged”处理,以执行除设置指定字段之外的其他操作。
public static class PropertySpecifiedExtensions2 { /// /// Bind the handler to automatically call each class's method on the property name. /// /// the entity to bind the autospecify event to public static void Autonotify(this IAutoNotifyPropertyChanged entity) { entity.PropertyChanged += (me, e) => ((IAutoNotifyPropertyChanged)me).WhenPropertyChanges(e.PropertyName); } /// /// Create a new entity and it's properties when changed /// /// /// public static T Create() where T : IAutoNotifyPropertyChanged, new() { var ret = new T(); ret.Autonotify(); return ret; } } /// /// Used by to standardize implementation behavior /// public interface IAutoNotifyPropertyChanged : INotifyPropertyChanged { void WhenPropertyChanges(string propertyName); }
然后每个类自己定义行为:
public partial class MyRandomClass: IAutoNotifyPropertyChanged { /// /// Create a new empty instance and its properties when changed /// /// public static MyRandomClass Create() { return PropertySpecifiedExtensions2.Create(); } public void WhenPropertyChanges(string propertyName) { switch (propertyName) { case "field1": this.field1Specified = true; return; // etc } // etc if(propertyName.StartsWith(...)) { /* do other stuff */ } } }
当然,这样做的缺点是属性名称的魔术字符串使重构变得困难,你可以通过Expression
解析来解决这个问题吗?