#define CMNATTR using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq.Expressions; // ReSharper disable RedundantUsingDirective using System.Linq; using System.Threading; // ReSharper restore RedundantUsingDirective #if CMNATTR using System.Runtime.CompilerServices; #endif namespace Plane.Copters { /// /// A base class for objects of which the properties must be observable (based on MvvmLight's ObservableObject). /// //// [ClassInfo(typeof(ViewModelBase))] public class PLObservableObject : INotifyPropertyChanged /*, INotifyPropertyChanging*/ { /// /// 与 UI 线程关联的 实例。 /// protected SynchronizationContext _uiSyncContext; /// /// 创建 的实例。 /// /// public PLObservableObject(SynchronizationContext uiSyncContext) { _uiSyncContext = uiSyncContext; } /// /// Occurs after a property value changes. /// public event PropertyChangedEventHandler PropertyChanged; /// /// Provides access to the PropertyChanged event handler to derived classes. /// protected PropertyChangedEventHandler PropertyChangedHandler { get { return PropertyChanged; } } #if !PORTABLE && !SL4 && !WINDOWS_UWP /// /// Occurs before a property value changes. /// public event PropertyChangingEventHandler PropertyChanging; /// /// Provides access to the PropertyChanging event handler to derived classes. /// protected PropertyChangingEventHandler PropertyChangingHandler { get { return PropertyChanging; } } #endif /// /// 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) { var myType = GetType(); #if NETFX_CORE if (!string.IsNullOrEmpty(propertyName) && myType.GetRuntimeProperty(propertyName) == null) { throw new ArgumentException("Property not found", propertyName); } #else if (!string.IsNullOrEmpty(propertyName) && myType.GetProperty(propertyName) == null) { #if !SILVERLIGHT var descriptor = this as ICustomTypeDescriptor; if (descriptor != null) { if (descriptor.GetProperties() .Cast() .Any(property => property.Name == propertyName)) { return; } } #endif throw new ArgumentException("Property not found", propertyName); } #endif } #if !PORTABLE && !SL4 && !WINDOWS_UWP #if CMNATTR /// /// Raises the PropertyChanging 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. [SuppressMessage( "Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This cannot be an event")] protected virtual void RaisePropertyChanging( [CallerMemberName] string propertyName = null) #else /// /// Raises the PropertyChanging 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. /// The name of the property that /// changed. [SuppressMessage( "Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This cannot be an event")] protected virtual void RaisePropertyChanging( string propertyName) #endif { VerifyPropertyName(propertyName); var handler = PropertyChanging; if (handler != null) { handler(this, new PropertyChangingEventArgs(propertyName)); } } #endif #if CMNATTR /// /// 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. [SuppressMessage( "Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This cannot be an event")] protected virtual void RaisePropertyChanged( [CallerMemberName] string propertyName = null) #else /// /// 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. /// The name of the property that /// changed. [SuppressMessage( "Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This cannot be an event")] protected virtual void RaisePropertyChanged( string propertyName) #endif { VerifyPropertyName(propertyName); var handler = PropertyChanged; if (handler != null) { #if NETFX_CORE var e = new PropertyChangedEventArgs(propertyName); if (SynchronizationContext.Current == _uiSyncContext) handler(this, e); else _uiSyncContext.Post(() => handler(this, e)); #else handler(this, new PropertyChangedEventArgs(propertyName)); #endif } } #if !PORTABLE && !SL4 && !WINDOWS_UWP /// /// Raises the PropertyChanging event if needed. /// /// The type of the property that /// changes. /// An expression identifying the property /// that changes. [SuppressMessage( "Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This cannot be an event")] [SuppressMessage( "Microsoft.Design", "CA1006:GenericMethodsShouldProvideTypeParameter", Justification = "This syntax is more convenient than other alternatives.")] protected virtual void RaisePropertyChanging(Expression> propertyExpression) { var handler = PropertyChanging; if (handler != null) { var propertyName = GetPropertyName(propertyExpression); handler(this, new PropertyChangingEventArgs(propertyName)); } } #endif /// /// Raises the PropertyChanged event if needed. /// /// The type of the property that /// changed. /// An expression identifying the property /// that changed. [SuppressMessage( "Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This cannot be an event")] [SuppressMessage( "Microsoft.Design", "CA1006:GenericMethodsShouldProvideTypeParameter", Justification = "This syntax is more convenient than other alternatives.")] protected virtual void RaisePropertyChanged(Expression> propertyExpression) { var handler = PropertyChanged; if (handler != null) { var propertyName = GetPropertyName(propertyExpression); #if NETFX_CORE var e = new PropertyChangedEventArgs(propertyName); if (SynchronizationContext.Current == _uiSyncContext) handler(this, e); else _uiSyncContext.Post(() => handler(this, e)); #else handler(this, new PropertyChangedEventArgs(propertyName)); #endif } } /// /// 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. [SuppressMessage( "Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "This syntax is more convenient than the alternatives."), SuppressMessage( "Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This syntax is more convenient than the alternatives.")] protected static string GetPropertyName(Expression> propertyExpression) { if (propertyExpression == null) { throw new ArgumentNullException("propertyExpression"); } var body = propertyExpression.Body as MemberExpression; if (body == null) { throw new ArgumentException("Invalid argument", "propertyExpression"); } var property = body.Member as PropertyInfo; if (property == null) { throw new ArgumentException("Argument is not a property", "propertyExpression"); } return property.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. [SuppressMessage( "Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This syntax is more convenient than the alternatives."), SuppressMessage( "Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "1#", Justification = "This syntax is more convenient than the alternatives.")] protected bool Set( Expression> propertyExpression, ref T field, T newValue) { if (EqualityComparer.Default.Equals(field, newValue)) { return false; } #if !PORTABLE && !SL4 && !WINDOWS_UWP RaisePropertyChanging(propertyExpression); #endif 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. [SuppressMessage( "Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "1#", Justification = "This syntax is more convenient than the alternatives.")] protected bool Set( string propertyName, ref T field, T newValue) { if (EqualityComparer.Default.Equals(field, newValue)) { return false; } #if !PORTABLE && !SL4 && !WINDOWS_UWP RaisePropertyChanging(propertyName); #endif field = newValue; // ReSharper disable ExplicitCallerInfoArgument RaisePropertyChanged(propertyName); // ReSharper restore ExplicitCallerInfoArgument return true; } #if CMNATTR /// /// 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); } #endif } }