|
|
|
Community Member
|
|
Looking at the INotifyPropertyChanged Implementation, two thoughts came up. 1. No way for the original class to implement INotifyPropertyChanged and fire the event. Common in WPF Model-View-ViewModel. This way the original class can respond to changes of its own properties. 2. The ability to check if the value of the property has changed before setting and firing the on change.
Solutions 1. Added a property to specify the method to call on the object for OnPropertyChanged. 2. Added a property to indicate whether to check equality first (defaults to true).
Code:
[Serializable] class OnPropertySetSubAspect : OnMethodBoundaryAspect { string propertyName; string onPropertyChanged; bool checkEqualBeforeSet;
public OnPropertySetSubAspect(string propertyName, NotifyPropertyChangedAttribute parent) { this.AspectPriority = parent.AspectPriority; this.onPropertyChanged = parent.OnPropertyChanged; this.checkEqualBeforeSet = parent.CheckEqualBeforeSet; this.propertyName = propertyName; }
[NonSerialized] MethodInfo getMethod; [NonSerialized] MethodInfo onPropertyChangedMethod;
public override void RuntimeInitialize(MethodBase method) { if (!string.IsNullOrEmpty(onPropertyChanged)) onPropertyChangedMethod = method.DeclaringType.GetMethod(onPropertyChanged, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (checkEqualBeforeSet) getMethod = method.DeclaringType.GetProperty(propertyName).GetGetMethod(); }
public override void OnEntry(MethodExecutionEventArgs eventArgs) { if (checkEqualBeforeSet) { object oldValue = getMethod.Invoke(eventArgs.Instance, null); object newValue = eventArgs.GetReadOnlyArgumentArray()[0]; if ((oldValue == null && newValue == null) || (oldValue != null && oldValue.Equals(newValue))) eventArgs.FlowBehavior = FlowBehavior.Return; } }
public override void OnSuccess(MethodExecutionEventArgs eventArgs) { if (onPropertyChangedMethod == null) { NotifyPropertyChangedImplementation implementation = (NotifyPropertyChangedImplementation)((IComposed<INotifyPropertyChanged>)eventArgs.Instance).GetImplementation(eventArgs.InstanceCredentials); implementation.OnPropertyChanged(propertyName); } else onPropertyChangedMethod.Invoke(eventArgs.Instance, new object[] { propertyName }); } }
[MulticastAttributeUsage(MulticastTargets.Class | MulticastTargets.Struct)] [Serializable] public sealed class NotifyPropertyChangedAttribute : CompoundAspect { [NonSerialized] private int aspectPriority = 0; [NonSerialized] private string onPropertyChanged = string.Empty; [NonSerialized] private bool checkEqualBeforeSet = true;
public override void ProvideAspects(object targetElement, LaosReflectionAspectCollection collection) { Type targetType = (Type)targetElement; // Add a OnMethodBoundaryAspect on each writable non-static property. foreach (PropertyInfo property in targetType.GetProperties()) { if (property.DeclaringType == targetType && property.CanWrite) { MethodInfo method = property.GetSetMethod(); if (!method.IsStatic) { collection.AddAspect(method, new OnPropertySetSubAspect(property.Name, this)); } } } if (string.IsNullOrEmpty(onPropertyChanged)) collection.AddAspect(targetType, new AddNotifyPropertyChangedInterfaceSubAspect()); //On the type, add a Composition aspect to implement the INotifyPropertyChanged interface else { //validate onPropertyChanged has correct signature MethodInfo mi = targetType.GetMethod(onPropertyChanged, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); if (mi == null) MessageSource.MessageSink.Write(new Message(SeverityType.Error, "CU0001", string.Format("OnPropertyChanged ({0}) method not found on type {1}", onPropertyChanged, targetType.Name), "CoreAspect")); else { ParameterInfo[] p = mi.GetParameters(); if (!(p.Length == 1 && p[0].ParameterType == typeof(string))) { MessageSource.MessageSink.Write(new Message(SeverityType.Error, "CU0001", string.Format("OnPropertyChanged method must take only 1 string parameter: {0}.{1}", targetType.Name, mi.Name), "CoreAspect")); } } } }
public int AspectPriority { get { return aspectPriority; } set { aspectPriority = value; } }
public string OnPropertyChanged { get { return onPropertyChanged; } set { onPropertyChanged = value; } }
public bool CheckEqualBeforeSet { get { return checkEqualBeforeSet; } set { checkEqualBeforeSet = value; } } }
OnPropertyChanged Usage:
[NotifyPropertyChanged(OnPropertyChanged="OnPropertyChanged")] public class Borrower : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } }
public string FirstName { get; set; } public string LastName { get; set; }
}
Brian Chance
|
|
|
|
|
Gael Fraiteur
SharpCrafters
|
|
Brian,
Thank you for your feedback.
I am aware of the limitations of PostSharp 1.5 regarding the implementation of INotifyPropertyChanged.
PostSharp 2.0 will come with a fully new design that elegantly solves this problem.
I'll be out in a couple of months.
-gael
|
|
|
|
|
Community Member
|
|
Great, cannot wait for it. Is there a roadmap for 2.0 anywhere?
Forgot to say earlier, great, great tool.
Brian
|
|
|
|
|
Gael Fraiteur
SharpCrafters
|
|
|
The roadmap currently is that I am targetting a first CTP before 15th October (it's a deadline because I will have a demo in Germany), hopefully before. I'm not sure yet for the stable release.
|
|
|
|
|
Community Member
|
|
Great implementation !
below, one mix of _implementation of the post above _of the Binding sample _of implementation presents on <!-- m --><a class="postlink" href="http://codepaste.net/3h4uqp">http://codepaste.net/3h4uqp</a><!-- m -->
It takes again the advantages of the 3: _if no implementation of INotifyPropertyChanged, auto interface implementation by aspect _if present and implement IFirePropertyChanged, automatic set of OnPropertyChanged variable _Can be set only one Property.
usage :
[NotifyPropertyChanged] public class Borrower2 { public string FirstName { get; set; } public string LastName { get; set; } }
or public class Borrower3 : IFirePropertyChanged, INotifyPropertyChanged { [RaisePropertyChanged] public string FirstName { get; set; } public string LastName { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } }
or [NotifyPropertyChanged(OnPropertyChanged = "OnPropertyChanged")] public class Borrower : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } }
public string FirstName { get; set; } public string LastName { get; set; } }
or [NotifyPropertyChanged] public class Borrower4 : IFirePropertyChanged, INotifyPropertyChanged { public string FirstName { get; set; } public string LastName { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } }
[size=150]implementation :[/size]
NotifyPropertyChangedAttribute.cs
/// <summary> /// Implementation of <see cref="CompositionAspect"/> that adds the <see cref="INotifyPropertyChanged"/> /// interface to the type to which it is applied. /// </summary> [Serializable] internal class AddNotifyPropertyChangedInterfaceSubAspect : CompositionAspect { /// <summary> /// Called at runtime, creates the implementation of the <see cref="INotifyPropertyChanged"/> interface. /// </summary> /// <param name="eventArgs">Execution context.</param> /// <returns>A new instance of <see cref="NotifyPropertyChangedImplementation"/>, which implements /// <see cref="INotifyPropertyChanged"/>.</returns> public override object CreateImplementationObject(InstanceBoundLaosEventArgs eventArgs) { return new NotifyPropertyChangedImplementation(eventArgs.Instance); }
/// <summary> /// Called at compile-time, gets the interface that should be publicly exposed. /// </summary> /// <param name="containerType">Type on which the interface will be implemented.</param> /// <returns></returns> public override Type GetPublicInterface(Type containerType) { return typeof(INotifyPropertyChanged); }
public override Type[] GetProtectedInterfaces(Type containerType) { return new Type[] { typeof(IFirePropertyChanged) }; }
/// <summary> /// Gets weaving options. /// </summary> /// <returns>Weaving options specifying that the implementation accessor interface (<see cref="IComposed{T}"/>) /// should be exposed, and that the implementation of interfaces should be silently ignored if they are /// already implemented in the parent types.</returns> public override CompositionAspectOptions GetOptions() { return CompositionAspectOptions.GenerateImplementationAccessor | CompositionAspectOptions.IgnoreIfAlreadyImplemented; } }
/// <summary> /// Implementation of the <see cref="INotifyPropertyChanged"/> interface. /// </summary> internal class NotifyPropertyChangedImplementation : INotifyPropertyChanged, IFirePropertyChanged { // Instance that exposes the current implementation. private readonly object instance;
/// <summary> /// Initializes a new <see cref="NotifyPropertyChangedImplementation"/> instance. /// </summary> /// <param name="instance">Instance that exposes the current implementation.</param> public NotifyPropertyChangedImplementation(object instance) { this.instance = instance; }
/// <summary> /// Event raised when a property is changed on the instance that /// exposes the current implementation. /// </summary> public event PropertyChangedEventHandler PropertyChanged;
/// <summary> /// Raises the <see cref="PropertyChanged"/> event. Called by the /// property-level aspect (<see cref="AddNotifyPropertyChangedInterfaceSubAspect"/>) /// at the end of property set accessors. /// </summary> /// <param name="propertyName">Name of the changed property.</param> public void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this.instance, new PropertyChangedEventArgs(propertyName)); } } }
/// <summary> /// Custom attribute that, when applied on a type (designated <i>target type</i>), implements the interface /// <see cref="INotifyPropertyChanged"/> and raises the <see cref="INotifyPropertyChanged.PropertyChanged"/> /// event when any property of the target type is modified. /// </summary> /// <remarks> /// Event raising is implemented by appending logic to the <b>set</b> accessor of properties. The /// <see cref="INotifyPropertyChanged.PropertyChanged"/> is raised only when accessors successfully complete. /// <para> /// Auto implement INotifyPropertyChanged for properties /// If class implement INotifyPropertyChanged set parameter OnPropertyChanged to changed method, /// if implement IFirePropertyChanged and INotifyPropertyChanged add nothing :) /// if implement nothing, it will be done, but not accessible without Post.Cast /// </para> /// </remarks> [MulticastAttributeUsage(MulticastTargets.Class | MulticastTargets.Struct)] [Serializable] public sealed class NotifyPropertyChangedAttribute : CompoundAspect { [NonSerialized] private int aspectPriority = 0; [NonSerialized] private string onPropertyChanged = string.Empty; [NonSerialized] private bool checkEqualBeforeSet = true;
/// <summary> /// Method called at compile time to get individual aspects required by the current compound /// aspect. /// </summary> /// <param name="targetElement">Metadata element (<see cref="Type"/> in our case) to which /// the current custom attribute instance is applied.</param> /// <param name="collection">Collection of aspects to which individual aspects should be /// added.</param> public override void ProvideAspects(object targetElement, LaosReflectionAspectCollection collection) { Type targetType = (Type)targetElement; if (string.IsNullOrEmpty(onPropertyChanged)) { if (targetType.GetInterface(typeof(INotifyPropertyChanged).FullName) != null) if (targetType.GetInterface(typeof(IFirePropertyChanged).FullName) != null) onPropertyChanged = "OnPropertyChanged"; else MessageSource.MessageSink.Write(new Message(SeverityType.Error, "CU0001", string.Format("OnPropertyChanged ({0}) method not found on type {1}, if type implement INotifyPropertyChanged precise OnPropertyChanged or implement IFirePropertyChanged", onPropertyChanged, targetType.Name), "CoreAspect"));
} if (string.IsNullOrEmpty(onPropertyChanged)) collection.AddAspect(targetType, new AddNotifyPropertyChangedInterfaceSubAspect()); //On the type, add a Composition aspect to implement the INotifyPropertyChanged interface else { //validate onPropertyChanged has correct signature MethodInfo mi = targetType.GetMethod(onPropertyChanged, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); if (mi == null) MessageSource.MessageSink.Write(new Message(SeverityType.Error, "CU0001", string.Format("OnPropertyChanged ({0}) method not found on type {1}", onPropertyChanged, targetType.Name), "CoreAspect")); else { ParameterInfo[] p = mi.GetParameters(); if (!(p.Length == 1 && p[0].ParameterType == typeof(string))) { MessageSource.MessageSink.Write(new Message(SeverityType.Error, "CU0001", string.Format("OnPropertyChanged method must take only 1 string parameter: {0}.{1}", targetType.Name, mi.Name), "CoreAspect")); } } }
// Add a OnMethodBoundaryAspect on each writable non-static property. foreach (PropertyInfo property in targetType.GetProperties()) { if (property.DeclaringType == targetType && property.CanWrite) { MethodInfo method = property.GetSetMethod(); if (!method.IsStatic) { collection.AddAspect(method, new RaisePropertyChangedAttribute(property.Name, this)); } } } }
/// <summary> /// Gets or sets the priority of the property-level aspect. /// </summary> /// <remarks> /// Give a large number to have the event raisen after any other /// on-success aspect on the properties of this type. The default value /// is 0. /// </remarks> public int AspectPriority { get { return aspectPriority; } set { aspectPriority = value; } }
public string OnPropertyChanged { get { return onPropertyChanged; } set { onPropertyChanged = value; } }
public bool CheckEqualBeforeSet { get { return checkEqualBeforeSet; } set { checkEqualBeforeSet = value; } } }
RaisePropertyChangedAttribute.cs /// <summary> /// Implementation of <see cref="OnMethodBoundaryAspect"/> that raises the /// <see cref="INotifyPropertyChanged.PropertyChanged"/> event when a property set /// accessor completes successfully. /// </summary> [MulticastAttributeUsage(MulticastTargets.Method)] [Serializable] public class RaisePropertyChangedAttribute : OnMethodBoundaryAspect { string propertyName; string onPropertyChanged; bool checkEqualBeforeSet;
public RaisePropertyChangedAttribute() { }
/// <summary> /// Initializes a new <see cref="RaisePropertyChangedAttribute"/>. /// </summary> /// <param name="propertyName">Name of the property to which this set accessor belong.</param> /// <param name="parent">Parent <see cref="NotifyPropertyChangedAttribute"/>.</param> public RaisePropertyChangedAttribute(string propertyName, NotifyPropertyChangedAttribute parent) { this.AspectPriority = parent.AspectPriority; this.OnPropertyChanged = parent.OnPropertyChanged; this.CheckEqualBeforeSet = parent.CheckEqualBeforeSet; this.propertyName = propertyName; }
[NonSerialized] MethodInfo getMethod;
[NonSerialized] MethodInfo onPropertyChangedMethod;
public string OnPropertyChanged { get { return onPropertyChanged; } set { onPropertyChanged = value; } }
public bool CheckEqualBeforeSet { get { return checkEqualBeforeSet; } set { checkEqualBeforeSet = value; } }
//public override void CompileTimeInitialize(MethodBase method) //{ // Type T = method.DeclaringType;
// if (T.GetInterface(typeof(INotifyPropertyChanged).FullName) == null) // { // string a = string.Empty; // foreach (var type in T.GetInterfaces()) // { // a += type.Name + ", "; // }
// throw new Exception(string.Format("Class {0} has an attribute RaisePropertyChanged but doesn't implements INotifyPropertyChanged!", T.Name + " " + a)); // } // base.CompileTimeInitialize(method);
//}
public override bool CompileTimeValidate(MethodBase method) { if (!string.IsNullOrEmpty(propertyName)) return true;
// Set propertyName if (IsPropertySetter(method)) { propertyName = GetPropertyName(method); } else return false;
// Set onPropertyChanged
Type targetType = method.DeclaringType; if (string.IsNullOrEmpty(onPropertyChanged)) { if (targetType.GetInterface(typeof(INotifyPropertyChanged).FullName) != null) if (targetType.GetInterface(typeof(IFirePropertyChanged).FullName) != null) onPropertyChanged = "OnPropertyChanged"; else { MessageSource.MessageSink.Write(new Message(SeverityType.Error, "CU0001", string.Format("OnPropertyChanged ({0}) method not found on type {1}, if type implement INotifyPropertyChanged precise OnPropertyChanged or implement IFirePropertyChanged", onPropertyChanged, targetType.Name), "CoreAspect")); return false; }
} if (string.IsNullOrEmpty(onPropertyChanged)) { MessageSource.MessageSink.Write(new Message(SeverityType.Error, "CU0001", string.Format("OnPropertyChanged ({0}) method not found on type {1}", onPropertyChanged, targetType.Name), "CoreAspect")); return false; } //validate onPropertyChanged has correct signature MethodInfo mi = targetType.GetMethod(onPropertyChanged, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); if (mi == null) { MessageSource.MessageSink.Write(new Message(SeverityType.Error, "CU0001", string.Format("OnPropertyChanged ({0}) method not found on type {1}", onPropertyChanged, targetType.Name), "CoreAspect")); return false; } else { ParameterInfo[] p = mi.GetParameters(); if (!(p.Length == 1 && p[0].ParameterType == typeof(string))) { MessageSource.MessageSink.Write(new Message(SeverityType.Error, "CU0001", string.Format("OnPropertyChanged method must take only 1 string parameter: {0}.{1}", targetType.Name, mi.Name), "CoreAspect")); return false; } } return true; }
private static string GetPropertyName(MethodBase method) { return method.Name.Replace("set_", ""); }
private static bool IsPropertySetter(MethodBase method) { return method.Name.StartsWith("set_"); }
public virtual PostSharp.Laos.OnMethodBoundaryAspectOptions GetOptions() { return OnMethodBoundaryAspectOptions.None; }
public override void RuntimeInitialize(MethodBase method) { if (!string.IsNullOrEmpty(onPropertyChanged)) onPropertyChangedMethod = method.DeclaringType.GetMethod(onPropertyChanged, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (checkEqualBeforeSet) getMethod = method.DeclaringType.GetProperty(propertyName).GetGetMethod(); }
/// <summary> /// Executed when the set accessor is entered. /// </summary> /// <param name="eventArgs">Event arguments with information about the /// current execution context.</param> public override void OnEntry(MethodExecutionEventArgs eventArgs) { if (checkEqualBeforeSet) { object oldValue = getMethod.Invoke(eventArgs.Instance, null); object newValue = eventArgs.GetReadOnlyArgumentArray()[0]; if ((oldValue == null && newValue == null) || (oldValue != null && oldValue.Equals(newValue))) eventArgs.FlowBehavior = FlowBehavior.Return; } }
/// <summary> /// Executed when the set accessor successfully completes. Raises the /// <see cref="INotifyPropertyChanged.PropertyChanged"/> event. /// </summary> /// <param name="eventArgs">Event arguments with information about the /// current execution context.</param> public override void OnSuccess(MethodExecutionEventArgs eventArgs) { if (onPropertyChangedMethod == null) { NotifyPropertyChangedImplementation implementation = (NotifyPropertyChangedImplementation)((IComposed<INotifyPropertyChanged>)eventArgs.Instance).GetImplementation(eventArgs.InstanceCredentials); implementation.OnPropertyChanged(propertyName); } else onPropertyChangedMethod.Invoke(eventArgs.Instance, new object[] { propertyName }); } }
Best regards, Alexandre Richonnier
|
|
|
|
|
Gael Fraiteur
SharpCrafters
|
|
|
|
|