#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
}
}