PropertyObserver.cs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq.Expressions;
  5. using System.Reflection;
  6. namespace HandyControl.Interactivity.Commands
  7. {
  8. /// <summary>
  9. /// Provide a way to observe property changes of INotifyPropertyChanged objects and invokes a
  10. /// custom action when the PropertyChanged event is fired.
  11. /// </summary>
  12. internal class PropertyObserver
  13. {
  14. private readonly Action _action;
  15. private PropertyObserver(System.Linq.Expressions.Expression propertyExpression, Action action)
  16. {
  17. _action = action;
  18. SubscribeListeners(propertyExpression);
  19. }
  20. private void SubscribeListeners(System.Linq.Expressions.Expression? propertyExpression)
  21. {
  22. var propNameStack = new Stack<PropertyInfo>();
  23. while (propertyExpression is MemberExpression temp) // Gets the root of the property chain.
  24. {
  25. propertyExpression = temp.Expression;
  26. if (temp.Member is PropertyInfo propertyInfo)
  27. {
  28. propNameStack.Push(propertyInfo); // Records the member info as property info
  29. }
  30. }
  31. if (propertyExpression is not ConstantExpression constantExpression)
  32. throw new NotSupportedException("Operation not supported for the given expression type. " +
  33. "Only MemberExpression and ConstantExpression are currently supported.");
  34. var propObserverNodeRoot = new PropertyObserverNode(propNameStack.Pop(), _action);
  35. PropertyObserverNode previousNode = propObserverNodeRoot;
  36. foreach (var propName in propNameStack) // Create a node chain that corresponds to the property chain.
  37. {
  38. var currentNode = new PropertyObserverNode(propName, _action);
  39. previousNode.Next = currentNode;
  40. previousNode = currentNode;
  41. }
  42. object? propOwnerObject = constantExpression.Value;
  43. if (propOwnerObject is not INotifyPropertyChanged inpcObject)
  44. throw new InvalidOperationException("Trying to subscribe PropertyChanged listener in object that " +
  45. $"owns '{propObserverNodeRoot.PropertyInfo.Name}' property, but the object does not implements INotifyPropertyChanged.");
  46. propObserverNodeRoot.SubscribeListenerFor(inpcObject);
  47. }
  48. /// <summary>
  49. /// Observes a property that implements INotifyPropertyChanged, and automatically calls a custom action on
  50. /// property changed notifications. The given expression must be in this form: "() => Prop.NestedProp.PropToObserve".
  51. /// </summary>
  52. /// <param name="propertyExpression">Expression representing property to be observed. Ex.: "() => Prop.NestedProp.PropToObserve".</param>
  53. /// <param name="action">Action to be invoked when PropertyChanged event occurs.</param>
  54. internal static PropertyObserver Observes<T>(Expression<Func<T>> propertyExpression, Action action)
  55. {
  56. return new PropertyObserver(propertyExpression.Body, action);
  57. }
  58. }
  59. }