using Dinah.Core; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.ComponentModel; namespace LibationFileManager { public partial class Configuration : INotifyPropertyChanging, INotifyPropertyChanged { public event PropertyChangingEventHandler PropertyChanging; public event PropertyChangedEventHandler PropertyChanged; private readonly Dictionary> propertyChangedActions = new(); private readonly Dictionary> propertyChangingActions = new(); /// /// Clear all subscriptions to PropertyChanged for /// public void ClearChangedSubscriptions(string propertyName) { if (propertyChangedActions.ContainsKey(propertyName) && propertyChangedActions[propertyName] is not null) propertyChangedActions[propertyName].Clear(); } /// /// Clear all subscriptions to PropertyChanging for /// public void ClearChangingSubscriptions(string propertyName) { if (propertyChangingActions.ContainsKey(propertyName) && propertyChangingActions[propertyName] is not null) propertyChangingActions[propertyName].Clear(); } /// /// Add an action to be executed when a property's value has changed /// /// The 's /// Name of the property whose change triggers the /// Action to be executed with parameters: and NewValue /// A reference to an interface that allows observers to stop receiving notifications before the provider has finished sending them. public IDisposable SubscribeToPropertyChanged(string propertyName, Action action) { validateSubscriber(propertyName, action); if (!propertyChangedActions.ContainsKey(propertyName)) propertyChangedActions.Add(propertyName, new List()); var actionlist = propertyChangedActions[propertyName]; if (!actionlist.Contains(action)) actionlist.Add(action); return new Unsubscriber(actionlist, action); } /// /// Add an action to be executed when a property's value is changing /// /// The 's /// Name of the property whose change triggers the /// Action to be executed with parameters: , OldValue, and NewValue /// A reference to an interface that allows observers to stop receiving notifications before the provider has finished sending them. public IDisposable SubscribeToPropertyChanging(string propertyName, Action action) { validateSubscriber(propertyName, action); if (!propertyChangingActions.ContainsKey(propertyName)) propertyChangingActions.Add(propertyName, new List()); var actionlist = propertyChangingActions[propertyName]; if (!actionlist.Contains(action)) actionlist.Add(action); return new Unsubscriber(actionlist, action); } private void validateSubscriber(string propertyName, MulticastDelegate action) { ArgumentValidator.EnsureNotNullOrWhiteSpace(propertyName, nameof(propertyName)); ArgumentValidator.EnsureNotNull(action, nameof(action)); var propertyInfo = GetType().GetProperty(propertyName); if (propertyInfo is null) throw new MissingMemberException($"{nameof(Configuration)}.{propertyName} does not exist."); if (propertyInfo.PropertyType != typeof(T)) throw new InvalidCastException($"{nameof(Configuration)}.{propertyName} is {propertyInfo.PropertyType}, but parameter is {typeof(T)}."); } private void Configuration_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e is PropertyChangedEventArgsEx args && propertyChangedActions.ContainsKey(args.PropertyName)) { foreach (var action in propertyChangedActions[args.PropertyName]) { action.DynamicInvoke(args.PropertyName, args.NewValue); } } } private void Configuration_PropertyChanging(object sender, PropertyChangingEventArgs e) { if (e is PropertyChangingEventArgsEx args && propertyChangingActions.ContainsKey(args.PropertyName)) { foreach (var action in propertyChangingActions[args.PropertyName]) { action.DynamicInvoke(args.PropertyName, args.OldValue, args.NewValue); } } } private class PropertyChangingEventArgsEx : PropertyChangingEventArgs { public object OldValue { get; } public object NewValue { get; } public PropertyChangingEventArgsEx(string propertyName, object oldValue, object newValue) : base(propertyName) { OldValue = oldValue; NewValue = newValue; } } private class PropertyChangedEventArgsEx : PropertyChangedEventArgs { public object NewValue { get; } public PropertyChangedEventArgsEx(string propertyName, object newValue) : base(propertyName) { NewValue = newValue; } } private class Unsubscriber : IDisposable { private List _observers; private MulticastDelegate _observer; internal Unsubscriber(List observers, MulticastDelegate observer) { _observers = observers; _observer = observer; } public void Dispose() { if (_observers.Contains(_observer)) _observers.Remove(_observer); } } /* * Use this type in the getter for any Dictionary settings, * and be sure to clone it before returning. This allows Configuration to * accurately detect if an of the Dictionary's elements have changed. */ private class EquatableDictionary : Dictionary { public EquatableDictionary(IEnumerable> keyValuePairs) : base(keyValuePairs) { } public EquatableDictionary Clone() => new(this); public override bool Equals(object obj) { if (obj is Dictionary dic && Count == dic.Count) { foreach (var pair in this) if (!dic.TryGetValue(pair.Key, out var value) || !pair.Value.Equals(value)) return false; return true; } return false; } public override int GetHashCode() => base.GetHashCode(); } } }