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