using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; namespace mt.Shared.Base { /// /// A base class for objects of which the properties must be observable. /// /// public class ObservableObject : INotifyPropertyChanged { private PropertyInfo[] _properties; [Browsable(false)] public PropertyInfo[] Properties => _properties ?? (_properties = GetType().GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)); /// /// Provides access to the PropertyChanged event handler to derived classes. /// /// protected PropertyChangedEventHandler PropertyChangedHandler => PropertyChanged; /// /// Occurs after a property value changes. /// /// public event PropertyChangedEventHandler PropertyChanged; /// /// Verifies that a property name exists in this ViewModel. This method /// can be called before the property is used, for instance before /// calling RaisePropertyChanged. It avoids errors when a property name /// is changed but some places are missed. /// /// /// /// /// This method is only active in DEBUG mode. /// /// The name of the property that will be /// checked. [Conditional("DEBUG")] [DebuggerStepThrough] public void VerifyPropertyName(string propertyName) { Type type = GetType(); if (!string.IsNullOrEmpty(propertyName) && type.GetTypeInfo().GetDeclaredProperty(propertyName) == null) throw new ArgumentException(@"Property not found", propertyName); } /// /// Raises the PropertyChanged event if needed. /// /// /// /// /// If the propertyName parameter /// does not correspond to an existing property on the current class, an /// exception is thrown in DEBUG configuration only. /// /// (optional) The name of the property that /// changed. protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler changedEventHandler = PropertyChanged; changedEventHandler?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// /// Raises the PropertyChanged event if needed. /// /// /// The type of the property that /// changed.An expression identifying the property /// that changed. protected virtual void RaisePropertyChanged(Expression> propertyExpression) { PropertyChangedEventHandler changedEventHandler = PropertyChanged; if (changedEventHandler == null) return; string propertyName = GetPropertyName(propertyExpression); changedEventHandler(this, new PropertyChangedEventArgs(propertyName)); } /// /// Extracts the name of a property from an expression. /// /// /// The type of the property.An expression returning the property's name. /// /// The name of the property returned by the expression. /// /// If the expression is null.If the expression does not represent a property. public string GetPropertyName(Expression> propertyExpression) { if (propertyExpression == null) throw new ArgumentNullException(nameof(propertyExpression)); if (!(propertyExpression.Body is MemberExpression memberExpression)) throw new ArgumentException(@"Invalid argument", nameof(propertyExpression)); var propertyInfo = memberExpression.Member as PropertyInfo; if (propertyInfo == null) throw new ArgumentException(@"Argument is not a property", nameof(propertyExpression)); return propertyInfo.Name; } /// /// Assigns a new value to the property. Then, raises the /// PropertyChanged event if needed. /// /// /// The type of the property that /// changed.An expression identifying the property /// that changed.The field storing the property's value.The property's value after the change /// occurred. /// /// True if the PropertyChanged event has been raised, /// false otherwise. The event is not raised if the old /// value is equal to the new value. /// protected bool Set(Expression> propertyExpression, ref T field, T newValue) { if (EqualityComparer.Default.Equals(field, newValue)) return false; field = newValue; RaisePropertyChanged(propertyExpression); return true; } /// /// Assigns a new value to the property. Then, raises the /// PropertyChanged event if needed. /// /// /// The type of the property that /// changed.The name of the property that /// changed.The field storing the property's value.The property's value after the change /// occurred. /// /// True if the PropertyChanged event has been raised, /// false otherwise. The event is not raised if the old /// value is equal to the new value. /// protected bool Set(string propertyName, ref T field, T newValue) { if (EqualityComparer.Default.Equals(field, newValue)) return false; field = newValue; // ReSharper disable once ExplicitCallerInfoArgument RaisePropertyChanged(propertyName); return true; } /// /// Assigns a new value to the property. Then, raises the /// PropertyChanged event if needed. /// /// /// The type of the property that /// changed.The field storing the property's value.The property's value after the change /// occurred.(optional) The name of the property that /// changed. /// /// True if the PropertyChanged event has been raised, /// false otherwise. The event is not raised if the old /// value is equal to the new value. /// protected bool Set(ref T field, T newValue, [CallerMemberName] string propertyName = null) { return Set(propertyName, ref field, newValue); } public string GetDisplayName(Expression> expression) { var propertyName = GetPropertyName(expression); return GetDisplayName(propertyName); } /// /// Returns the display name of a property via it's string name /// /// /// public string GetDisplayName(string propertyName) { var propertyInfo = (from p in Properties where p.Name == propertyName select p).FirstOrDefault(); return GetDisplayName(propertyInfo); } /// /// Returns the display name of a property via propertyInfo /// /// /// public string GetDisplayName(PropertyInfo propertyInfo) { var displayAttribute = Attribute.GetCustomAttributes(propertyInfo, typeof(DisplayAttribute)).FirstOrDefault() as DisplayAttribute; if (displayAttribute != null) { return displayAttribute.GetName(); } return propertyInfo.Name; } } }