Created
March 13, 2015 19:38
-
-
Save owen2/c0beb60fd3cbeab41336 to your computer and use it in GitHub Desktop.
The closest thing to magic change notification on automatic properties without using proxies or postcompilers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Runtime.CompilerServices; | |
namespace ViewModelBase | |
{ | |
/// <summary> | |
/// Implementation of INotifyPropertyChanged. | |
/// </summary> | |
public abstract class ChangeNotificationObject : INotifyPropertyChanged | |
{ | |
private IDictionary<string, IField> _valueCache = new Dictionary<string, IField>(); | |
/// <summary> | |
/// Raises the PropertyChanged event which will trigger the view to sync the changed properties. | |
/// </summary> | |
/// <param name="propertyNames">One or more strings representing property names.</param> | |
protected void NotifyPropertyChanged(params string[] propertyNames) | |
{ | |
if (PropertyChanged == null) return; // No one is listening | |
if (propertyNames.Length == 0) | |
PropertyChanged(this, new PropertyChangedEventArgs("")); //Notify All | |
foreach (var name in propertyNames) | |
PropertyChanged(this, new PropertyChangedEventArgs(name)); | |
} | |
/// <summary> | |
/// Fires the property changed event. | |
/// </summary> | |
/// <param name="exprs">One or more Property selector lambdas </param> | |
protected void NotifyPropertyChanged(params Expression<Func<object>>[] exprs) | |
{ | |
NotifyPropertyChanged(exprs.Select(selector => GetPropertyName(selector)).ToArray()); | |
} | |
/// <summary> | |
/// Notifies view to update all bindings on this object. | |
/// </summary> | |
protected void NotifyAllPropertiesChanged() | |
{ | |
NotifyPropertyChanged(); | |
} | |
/// <summary> | |
/// Occurs when a property has changed. | |
/// </summary> | |
public event PropertyChangedEventHandler PropertyChanged; | |
/// <summary> | |
/// Gets the value for the property. | |
/// </summary> | |
/// <typeparam name="T">The property value type.</typeparam> | |
/// <param name="propertyName">[Auto populated]The name of the property.</param> | |
/// <returns>The value of the property.</returns> | |
public T GetValue<T>([CallerMemberName]string propertyName = "") | |
{ | |
return GetField<T>(propertyName).Value; | |
} | |
/// <summary> | |
/// Updates the property with the new value. | |
/// </summary> | |
/// <typeparam name="T">Type of the property</typeparam> | |
/// <param name="value">The new value</param> | |
/// <param name="onPropertyChanging">Predicate to run to see if the property should be changed.</param> | |
/// <param name="onPropertyChanged">Occurs after the property has changed.</param> | |
/// <param name="propertyName">[Auto populated]The name of the property. Do not specify this property.</param> | |
/// <returns><c>true</c> if the property's value was updated.</returns> | |
protected virtual bool SetValue<T>(T value, Predicate<T> onPropertyChanging = null, Action onPropertyChanged = null, [CallerMemberName] string propertyName = "") | |
{ | |
IField<T> field = GetField<T>(propertyName); | |
bool changed = false; | |
if (propertyName != null) | |
{ | |
if (!EqualityComparer<T>.Default.Equals(value, field.Value)) | |
{ | |
bool shouldContinue = onPropertyChanging == null || onPropertyChanging(value); | |
if (shouldContinue) | |
{ | |
field.Value = value; | |
NotifyPropertyChanged(propertyName); | |
if (onPropertyChanged != null) | |
onPropertyChanged(); | |
changed = true; | |
} | |
} | |
} | |
return changed; | |
} | |
private IField<T> GetField<T>(string propertyName) | |
{ | |
IField<T> field = null; | |
if (!_valueCache.ContainsKey(propertyName)) | |
{ | |
field = new Field<T>(); | |
_valueCache.Add(propertyName, field); | |
} | |
else | |
{ | |
field = (IField<T>)_valueCache[propertyName]; | |
} | |
return field; | |
} | |
/// <summary> | |
/// Returns the property name of the given expression. | |
/// </summary> | |
protected string GetPropertyName<T>(Expression<Func<T, object>> property) | |
{ | |
var memberExpression = property.Body as MemberExpression; | |
if (memberExpression == null) | |
{ | |
var convert = property.Body as UnaryExpression; | |
} | |
return memberExpression.Member.Name; | |
} | |
/// <summary> | |
/// Returns the property name of the given expression. | |
/// </summary> | |
protected string GetPropertyName<T, U>(Expression<Func<T, U>> property) | |
{ | |
var memberExpression = property.Body as MemberExpression; | |
if (memberExpression == null) | |
throw new ArgumentException("Use this to select a property on the type " + typeof(T), "property"); | |
return memberExpression.Member.Name; | |
} | |
/// <summary> | |
/// Returns the property name of the given expression. | |
/// </summary> | |
protected string GetPropertyName<T>(Expression<Func<T>> property) | |
{ | |
var memberExpression = property.Body as MemberExpression; | |
if (memberExpression == null) | |
{ | |
UnaryExpression ue = property.Body as UnaryExpression; | |
if (ue != null) | |
memberExpression = ue.Operand as MemberExpression; | |
} | |
if (memberExpression == null) | |
throw new ArgumentException("property"); | |
return memberExpression.Member.Name; | |
} | |
#region Implementation | |
internal class Field<T> : IField<T> | |
{ | |
public T Value { get; set; } | |
public static implicit operator T(Field<T> t) | |
{ | |
return t.Value; | |
} | |
} | |
internal interface IField | |
{ | |
} | |
internal interface IField<T> : IField | |
{ | |
T Value { get; set; } | |
} | |
#endregion Implementation | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment