Permalink
Fetching contributors…
Cannot retrieve contributors at this time
426 lines (389 sloc) 15.8 KB
// ****************************************************************************
// <copyright file="ObservableObject.cs" company="GalaSoft Laurent Bugnion">
// Copyright © GalaSoft Laurent Bugnion 2011-2016
// </copyright>
// ****************************************************************************
// <author>Laurent Bugnion</author>
// <email>laurent@galasoft.ch</email>
// <date>10.4.2011</date>
// <project>GalaSoft.MvvmLight.Messaging</project>
// <web>http://www.mvvmlight.net</web>
// <license>
// See license.txt in this project or http://www.galasoft.ch/license_MIT.txt
// </license>
// ****************************************************************************
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;
// ReSharper restore RedundantUsingDirective
#if CMNATTR
using System.Runtime.CompilerServices;
#endif
namespace GalaSoft.MvvmLight
{
/// <summary>
/// A base class for objects of which the properties must be observable.
/// </summary>
//// [ClassInfo(typeof(ViewModelBase))]
public class ObservableObject : INotifyPropertyChanged /*, INotifyPropertyChanging*/
{
/// <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
/// <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
var info = myType.GetTypeInfo();
if (!string.IsNullOrEmpty(propertyName)
&& info.GetDeclaredProperty(propertyName) == null)
{
// Check base types
var found = false;
while (info.BaseType != typeof(Object))
{
info = info.BaseType.GetTypeInfo();
if (info.GetDeclaredProperty(propertyName) != null)
{
found = true;
break;
}
}
if (!found)
{
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
#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")]
public 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")]
public 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")]
public 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")]
public virtual void RaisePropertyChanged(
string propertyName)
#endif
{
VerifyPropertyName(propertyName);
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#if !PORTABLE && !SL4
/// <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.")]
public 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.")]
public virtual void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
var handler = PropertyChanged;
if (handler != null)
{
var propertyName = GetPropertyName(propertyExpression);
if (!string.IsNullOrEmpty(propertyName))
{
// ReSharper disable once ExplicitCallerInfoArgument
RaisePropertyChanged(propertyName);
}
}
}
/// <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
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
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
}
}