414 lines
15 KiB
C#
414 lines
15 KiB
C#
#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
|
|
{
|
|
/// <summary>
|
|
/// A base class for objects of which the properties must be observable (based on MvvmLight's ObservableObject).
|
|
/// </summary>
|
|
//// [ClassInfo(typeof(ViewModelBase))]
|
|
public class PLObservableObject : INotifyPropertyChanged /*, INotifyPropertyChanging*/
|
|
{
|
|
/// <summary>
|
|
/// 与 UI 线程关联的 <see cref="SynchronizationContext"/> 实例。
|
|
/// </summary>
|
|
protected SynchronizationContext _uiSyncContext;
|
|
|
|
/// <summary>
|
|
/// 创建 <see cref="EHObservableObject"/> 的实例。
|
|
/// </summary>
|
|
/// <param name="uiSyncContext"></param>
|
|
public PLObservableObject(SynchronizationContext uiSyncContext)
|
|
{
|
|
_uiSyncContext = uiSyncContext;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs after a property value changes.
|
|
/// </summary>
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
/// <summary>
|
|
/// Provides access to the PropertyChanged event handler to derived classes.
|
|
/// </summary>
|
|
protected PropertyChangedEventHandler PropertyChangedHandler
|
|
{
|
|
get
|
|
{
|
|
return PropertyChanged;
|
|
}
|
|
}
|
|
|
|
#if !PORTABLE && !SL4 && !WINDOWS_UWP
|
|
/// <summary>
|
|
/// Occurs before a property value changes.
|
|
/// </summary>
|
|
public event PropertyChangingEventHandler PropertyChanging;
|
|
|
|
/// <summary>
|
|
/// Provides access to the PropertyChanging event handler to derived classes.
|
|
/// </summary>
|
|
protected PropertyChangingEventHandler PropertyChangingHandler
|
|
{
|
|
get
|
|
{
|
|
return PropertyChanging;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <remarks>This method is only active in DEBUG mode.</remarks>
|
|
/// <param name="propertyName">The name of the property that will be
|
|
/// checked.</param>
|
|
[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<PropertyDescriptor>()
|
|
.Any(property => property.Name == propertyName))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
throw new ArgumentException("Property not found", propertyName);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if !PORTABLE && !SL4 && !WINDOWS_UWP
|
|
#if CMNATTR
|
|
/// <summary>
|
|
/// Raises the PropertyChanging event if needed.
|
|
/// </summary>
|
|
/// <remarks>If the propertyName parameter
|
|
/// does not correspond to an existing property on the current class, an
|
|
/// exception is thrown in DEBUG configuration only.</remarks>
|
|
/// <param name="propertyName">(optional) The name of the property that
|
|
/// changed.</param>
|
|
[SuppressMessage(
|
|
"Microsoft.Design",
|
|
"CA1030:UseEventsWhereAppropriate",
|
|
Justification = "This cannot be an event")]
|
|
protected virtual void RaisePropertyChanging(
|
|
[CallerMemberName] string propertyName = null)
|
|
#else
|
|
/// <summary>
|
|
/// Raises the PropertyChanging event if needed.
|
|
/// </summary>
|
|
/// <remarks>If the propertyName parameter
|
|
/// does not correspond to an existing property on the current class, an
|
|
/// exception is thrown in DEBUG configuration only.</remarks>
|
|
/// <param name="propertyName">The name of the property that
|
|
/// changed.</param>
|
|
[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
|
|
/// <summary>
|
|
/// Raises the PropertyChanged event if needed.
|
|
/// </summary>
|
|
/// <remarks>If the propertyName parameter
|
|
/// does not correspond to an existing property on the current class, an
|
|
/// exception is thrown in DEBUG configuration only.</remarks>
|
|
/// <param name="propertyName">(optional) The name of the property that
|
|
/// changed.</param>
|
|
[SuppressMessage(
|
|
"Microsoft.Design",
|
|
"CA1030:UseEventsWhereAppropriate",
|
|
Justification = "This cannot be an event")]
|
|
protected virtual void RaisePropertyChanged(
|
|
[CallerMemberName] string propertyName = null)
|
|
#else
|
|
/// <summary>
|
|
/// Raises the PropertyChanged event if needed.
|
|
/// </summary>
|
|
/// <remarks>If the propertyName parameter
|
|
/// does not correspond to an existing property on the current class, an
|
|
/// exception is thrown in DEBUG configuration only.</remarks>
|
|
/// <param name="propertyName">The name of the property that
|
|
/// changed.</param>
|
|
[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
|
|
/// <summary>
|
|
/// Raises the PropertyChanging event if needed.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the property that
|
|
/// changes.</typeparam>
|
|
/// <param name="propertyExpression">An expression identifying the property
|
|
/// that changes.</param>
|
|
[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<T>(Expression<Func<T>> propertyExpression)
|
|
{
|
|
var handler = PropertyChanging;
|
|
if (handler != null)
|
|
{
|
|
var propertyName = GetPropertyName(propertyExpression);
|
|
handler(this, new PropertyChangingEventArgs(propertyName));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Raises the PropertyChanged event if needed.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the property that
|
|
/// changed.</typeparam>
|
|
/// <param name="propertyExpression">An expression identifying the property
|
|
/// that changed.</param>
|
|
[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<T>(Expression<Func<T>> 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
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts the name of a property from an expression.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the property.</typeparam>
|
|
/// <param name="propertyExpression">An expression returning the property's name.</param>
|
|
/// <returns>The name of the property returned by the expression.</returns>
|
|
/// <exception cref="ArgumentNullException">If the expression is null.</exception>
|
|
/// <exception cref="ArgumentException">If the expression does not represent a property.</exception>
|
|
[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<T>(Expression<Func<T>> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assigns a new value to the property. Then, raises the
|
|
/// PropertyChanged event if needed.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the property that
|
|
/// changed.</typeparam>
|
|
/// <param name="propertyExpression">An expression identifying the property
|
|
/// that changed.</param>
|
|
/// <param name="field">The field storing the property's value.</param>
|
|
/// <param name="newValue">The property's value after the change
|
|
/// occurred.</param>
|
|
/// <returns>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.</returns>
|
|
[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<T>(
|
|
Expression<Func<T>> propertyExpression,
|
|
ref T field,
|
|
T newValue)
|
|
{
|
|
if (EqualityComparer<T>.Default.Equals(field, newValue))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#if !PORTABLE && !SL4 && !WINDOWS_UWP
|
|
RaisePropertyChanging(propertyExpression);
|
|
#endif
|
|
field = newValue;
|
|
RaisePropertyChanged(propertyExpression);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assigns a new value to the property. Then, raises the
|
|
/// PropertyChanged event if needed.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the property that
|
|
/// changed.</typeparam>
|
|
/// <param name="propertyName">The name of the property that
|
|
/// changed.</param>
|
|
/// <param name="field">The field storing the property's value.</param>
|
|
/// <param name="newValue">The property's value after the change
|
|
/// occurred.</param>
|
|
/// <returns>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.</returns>
|
|
[SuppressMessage(
|
|
"Microsoft.Design",
|
|
"CA1045:DoNotPassTypesByReference",
|
|
MessageId = "1#",
|
|
Justification = "This syntax is more convenient than the alternatives.")]
|
|
protected bool Set<T>(
|
|
string propertyName,
|
|
ref T field,
|
|
T newValue)
|
|
{
|
|
if (EqualityComparer<T>.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
|
|
/// <summary>
|
|
/// Assigns a new value to the property. Then, raises the
|
|
/// PropertyChanged event if needed.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the property that
|
|
/// changed.</typeparam>
|
|
/// <param name="field">The field storing the property's value.</param>
|
|
/// <param name="newValue">The property's value after the change
|
|
/// occurred.</param>
|
|
/// <param name="propertyName">(optional) The name of the property that
|
|
/// changed.</param>
|
|
/// <returns>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.</returns>
|
|
protected bool Set<T>(
|
|
ref T field,
|
|
T newValue,
|
|
[CallerMemberName] string propertyName = null)
|
|
{
|
|
return Set(propertyName, ref field, newValue);
|
|
}
|
|
#endif
|
|
}
|
|
} |