using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Text; using System.Threading; using System.Windows.Input; namespace HandyControl.Interactivity.Commands { /// /// An whose delegates can be attached for and . /// public abstract class DelegateCommandBase : BindableBase, ICommand, IActiveAware { private bool _isActive; private SynchronizationContext? _synchronizationContext; private readonly HashSet _observedPropertiesExpressions = new(); /// /// Provides an Exception Handler to register callbacks or handle encountered exceptions within /// protected readonly MulticastExceptionHandler ExceptionHandler = new(); /// /// Creates a new instance of a , specifying both the execute action and the can execute function. /// protected DelegateCommandBase() { _synchronizationContext = SynchronizationContext.Current; } /// /// Occurs when changes occur that affect whether or not the command should execute. /// public virtual event EventHandler? CanExecuteChanged; /// /// Raises so every /// command invoker can re-query . /// protected virtual void OnCanExecuteChanged() { var handler = CanExecuteChanged; if (handler != null) { if (_synchronizationContext != null && _synchronizationContext != SynchronizationContext.Current) _synchronizationContext.Post((o) => handler.Invoke(this, EventArgs.Empty), null); else handler.Invoke(this, EventArgs.Empty); } } /// /// Raises so every command invoker /// can re-query to check if the command can execute. /// /// Note that this will trigger the execution of once for each invoker. [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] public void RaiseCanExecuteChanged() { OnCanExecuteChanged(); } void ICommand.Execute(object? parameter) { Execute(parameter); } bool ICommand.CanExecute(object? parameter) { return CanExecute(parameter); } /// /// Handle the internal invocation of /// /// Command Parameter protected abstract void Execute(object? parameter); /// /// Handle the internal invocation of /// /// /// if the Command Can Execute, otherwise protected abstract bool CanExecute(object? parameter); /// /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. /// /// The object type containing the property specified in the expression. /// The property expression. Example: ObservesProperty(() => PropertyName). protected internal void ObservesPropertyInternal(Expression> propertyExpression) { if (_observedPropertiesExpressions.Contains(propertyExpression.ToString())) { throw new ArgumentException($"{propertyExpression} is already being observed.", nameof(propertyExpression)); } else { _observedPropertiesExpressions.Add(propertyExpression.ToString()); PropertyObserver.Observes(propertyExpression, RaiseCanExecuteChanged); } } #region IsActive /// /// Gets or sets a value indicating whether the object is active. /// /// if the object is active; otherwise . public bool IsActive { get => _isActive; set => SetProperty(ref _isActive, value, OnIsActiveChanged); } /// /// Fired if the property changes. /// public virtual event EventHandler? IsActiveChanged; /// /// This raises the event. /// protected virtual void OnIsActiveChanged() { IsActiveChanged?.Invoke(this, EventArgs.Empty); } #endregion } }