using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq.Expressions; using System.Reflection; namespace HandyControl.Interactivity.Commands { /// /// Provide a way to observe property changes of INotifyPropertyChanged objects and invokes a /// custom action when the PropertyChanged event is fired. /// internal class PropertyObserver { private readonly Action _action; private PropertyObserver(System.Linq.Expressions.Expression propertyExpression, Action action) { _action = action; SubscribeListeners(propertyExpression); } private void SubscribeListeners(System.Linq.Expressions.Expression? propertyExpression) { var propNameStack = new Stack(); while (propertyExpression is MemberExpression temp) // Gets the root of the property chain. { propertyExpression = temp.Expression; if (temp.Member is PropertyInfo propertyInfo) { propNameStack.Push(propertyInfo); // Records the member info as property info } } if (propertyExpression is not ConstantExpression constantExpression) throw new NotSupportedException("Operation not supported for the given expression type. " + "Only MemberExpression and ConstantExpression are currently supported."); var propObserverNodeRoot = new PropertyObserverNode(propNameStack.Pop(), _action); PropertyObserverNode previousNode = propObserverNodeRoot; foreach (var propName in propNameStack) // Create a node chain that corresponds to the property chain. { var currentNode = new PropertyObserverNode(propName, _action); previousNode.Next = currentNode; previousNode = currentNode; } object? propOwnerObject = constantExpression.Value; if (propOwnerObject is not INotifyPropertyChanged inpcObject) throw new InvalidOperationException("Trying to subscribe PropertyChanged listener in object that " + $"owns '{propObserverNodeRoot.PropertyInfo.Name}' property, but the object does not implements INotifyPropertyChanged."); propObserverNodeRoot.SubscribeListenerFor(inpcObject); } /// /// Observes a property that implements INotifyPropertyChanged, and automatically calls a custom action on /// property changed notifications. The given expression must be in this form: "() => Prop.NestedProp.PropToObserve". /// /// Expression representing property to be observed. Ex.: "() => Prop.NestedProp.PropToObserve". /// Action to be invoked when PropertyChanged event occurs. internal static PropertyObserver Observes(Expression> propertyExpression, Action action) { return new PropertyObserver(propertyExpression.Body, action); } } }