using Dinah.Core; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; namespace LibationFileManager { #region Useage /* * USEAGE ************************* * * * Event Filter Mode * * * ************************* propertyChangeFilter.PropertyChanged += MyPropertiesChanged; [PropertyChangeFilter("MyProperty1")] [PropertyChangeFilter("MyProperty2")] void MyPropertiesChanged(object sender, PropertyChangedEventArgsEx e) { // Only properties whose names match either "MyProperty1" // or "MyProperty2" will fire this event handler. } ****** * OR * ****** propertyChangeFilter.PropertyChanged += [PropertyChangeFilter("MyProperty1")] [PropertyChangeFilter("MyProperty2")] (_, _) => { // Only properties whose names match either "MyProperty1" // or "MyProperty2" will fire this event handler. }; ************************* * * * Observable Mode * * * ************************* using var cancellation = propertyChangeFilter.ObservePropertyChanging("MyProperty", MyPropertyChanging); void MyPropertyChanging(string propertyName, int oldValue, int newValue) { // Only the property whose name match // "MyProperty" will fire this method. } //The observer is delisted when cancellation is disposed ****** * OR * ****** using var cancellation = propertyChangeFilter.ObservePropertyChanged("MyProperty", (_, s) => { // Only the property whose name match // "MyProperty" will fire this action. }); //The observer is delisted when cancellation is disposed */ #endregion public abstract class PropertyChangeFilter { private readonly Dictionary> propertyChangedActions = new(); private readonly Dictionary> propertyChangingActions = new(); private readonly List> changedFilters = new(); private readonly List> changingFilters = new(); public PropertyChangeFilter() { PropertyChanging += Configuration_PropertyChanging; PropertyChanged += Configuration_PropertyChanged; } #region Events protected void OnPropertyChanged(string propertyName, object newValue) => _propertyChanged?.Invoke(this, new(propertyName, newValue)); protected void OnPropertyChanging(string propertyName, object oldValue, object newValue) => _propertyChanging?.Invoke(this, new(propertyName, oldValue, newValue)); private PropertyChangedEventHandlerEx _propertyChanged; private PropertyChangingEventHandlerEx _propertyChanging; public event PropertyChangedEventHandlerEx PropertyChanged { add { var attributes = Attribute.GetCustomAttributes(value.Method, typeof(PropertyChangeFilterAttribute)) as PropertyChangeFilterAttribute[]; if (attributes.Any()) { var matches = attributes.Select(a => a.PropertyName).ToArray(); void filterer(object s, PropertyChangedEventArgsEx e) { if (e.PropertyName.In(matches)) value(s, e); } changedFilters.Add(new(value, filterer)); _propertyChanged += filterer; } else _propertyChanged += value; } remove { var del = changedFilters.LastOrDefault(d => d.Key == value); if (del.Key is null) _propertyChanged -= value; else { _propertyChanged -= del.Value; changedFilters.Remove(del); } } } public event PropertyChangingEventHandlerEx PropertyChanging { add { var attributes = Attribute.GetCustomAttributes(value.Method, typeof(PropertyChangeFilterAttribute)) as PropertyChangeFilterAttribute[]; if (attributes.Any()) { var matches = attributes.Select(a => a.PropertyName).ToArray(); void filterer(object s, PropertyChangingEventArgsEx e) { if (e.PropertyName.In(matches)) value(s, e); } changingFilters.Add(new(value, filterer)); _propertyChanging += filterer; } else _propertyChanging += value; } remove { var del = changingFilters.LastOrDefault(d => d.Key == value); if (del.Key is null) _propertyChanging -= value; else { _propertyChanging -= del.Value; changingFilters.Remove(del); } } } #endregion #region Observables /// /// 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 ObservePropertyChanged(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 ObservePropertyChanging(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, Delegate 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, PropertyChangedEventArgsEx e) { if (propertyChangedActions.ContainsKey(e.PropertyName)) { foreach (var action in propertyChangedActions[e.PropertyName]) { action.DynamicInvoke(e.PropertyName, e.NewValue); } } } private void Configuration_PropertyChanging(object sender, PropertyChangingEventArgsEx e) { if (propertyChangingActions.ContainsKey(e.PropertyName)) { foreach (var action in propertyChangingActions[e.PropertyName]) { action.DynamicInvoke(e.PropertyName, e.OldValue, e.NewValue); } } } private class Unsubscriber : IDisposable { private List _observers; private Delegate _observer; internal Unsubscriber(List observers, Delegate observer) { _observers = observers; _observer = observer; } public void Dispose() { if (_observers.Contains(_observer)) _observers.Remove(_observer); } } #endregion } public delegate void PropertyChangedEventHandlerEx(object sender, PropertyChangedEventArgsEx e); public delegate void PropertyChangingEventHandlerEx(object sender, PropertyChangingEventArgsEx e); public class PropertyChangedEventArgsEx : PropertyChangedEventArgs { public object NewValue { get; } public PropertyChangedEventArgsEx(string propertyName, object newValue) : base(propertyName) { NewValue = newValue; } } public 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; } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class PropertyChangeFilterAttribute : Attribute { public string PropertyName { get; } public PropertyChangeFilterAttribute(string propertyName) { PropertyName = propertyName; } } }