diff --git a/MVVM/DelegateCommand.cs b/MVVM/DelegateCommand.cs new file mode 100644 index 0000000..3d14b55 --- /dev/null +++ b/MVVM/DelegateCommand.cs @@ -0,0 +1,297 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Windows; +using System.Windows.Input; +using System.Windows.Threading; + +namespace Command +{ + + /// + /// Provides an implementation which relays the Execute and CanExecute + /// method to the specified delegates. + /// + public class DelegateCommand : ICommand + { + #region Static disabled command + + /// + /// A instance that does nothing and can never be executed. + /// + public static readonly DelegateCommand Disabled = new DelegateCommand(() => { }) { IsEnabled = false }; + + #endregion Static disabled command + + #region Private data + + private readonly Action execute; + private readonly Func canExecute; + private List weakHandlers; + private bool? isEnabled; + private bool raiseCanExecuteChangedPending; + + #endregion Private data + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// Delegate to execute when Execute is called on the command. + /// The execute argument must not be null. + public DelegateCommand(Action execute) + : this(execute != null ? p => execute() : (Action)null, (Func)null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Delegate to execute when Execute is called on the command. + /// Delegate to execute when CanExecute is called on the command. + /// The execute argument must not be null. + public DelegateCommand(Action execute, Func canExecute) + : this(execute != null ? p => execute() : (Action)null, canExecute != null ? p => canExecute() : (Func)null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Delegate to execute when Execute is called on the command. + /// Delegate to execute when CanExecute is called on the command. + /// The execute argument must not be null. + public DelegateCommand(Action execute, Func canExecute) + : this(execute != null ? p => execute() : (Action)null, canExecute) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Delegate to execute when Execute is called on the command. + /// The execute argument must not be null. + public DelegateCommand(Action execute) + : this(execute, (Func)null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Delegate to execute when Execute is called on the command. + /// Delegate to execute when CanExecute is called on the command. + /// The execute argument must not be null. + public DelegateCommand(Action execute, Func canExecute) + : this(execute, canExecute != null ? p => canExecute() : (Func)null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Delegate to execute when Execute is called on the command. + /// Delegate to execute when CanExecute is called on the command. + /// The execute argument must not be null. + public DelegateCommand(Action execute, Func canExecute) + { + if (execute == null) + throw new ArgumentNullException(nameof(execute)); + + this.execute = execute; + this.canExecute = canExecute; + } + + #endregion Constructors + + #region CanExecuteChanged event + + /// + /// Occurs when changes occur that affect whether or not the command should execute. + /// + public event EventHandler CanExecuteChanged + { + add + { + if (weakHandlers == null) + { + weakHandlers = new List(new[] { new WeakReference(value) }); + } + else + { + weakHandlers.Add(new WeakReference(value)); + } + } + remove + { + if (weakHandlers == null) return; + + for (int i = weakHandlers.Count - 1; i >= 0; i--) + { + WeakReference weakReference = weakHandlers[i]; + EventHandler handler = weakReference.Target as EventHandler; + if (handler == null || handler == value) + { + weakHandlers.RemoveAt(i); + } + } + } + } + + /// + /// Raises the event. + /// + [DebuggerStepThrough] + public void RaiseCanExecuteChanged() => OnCanExecuteChanged(); + + /// + /// Raises the event after all other processing has + /// finished. Multiple calls to this function before the asynchronous action has been + /// started are ignored. + /// + [DebuggerStepThrough] + public void RaiseCanExecuteChangedAsync() + { + if (!raiseCanExecuteChangedPending) + { + // Don't do anything if not on the UI thread. The dispatcher event will never be + // fired there and probably there's nobody interested in changed properties anyway + // on that thread. + if (Dispatcher.CurrentDispatcher == Application.Current.Dispatcher) + { + Dispatcher.CurrentDispatcher.BeginInvoke((Action)OnCanExecuteChanged, DispatcherPriority.Loaded); + raiseCanExecuteChangedPending = true; + } + } + } + + /// + /// Raises the event. + /// + [DebuggerStepThrough] + protected virtual void OnCanExecuteChanged() + { + raiseCanExecuteChangedPending = false; + PurgeWeakHandlers(); + if (weakHandlers == null) return; + + WeakReference[] handlers = weakHandlers.ToArray(); + foreach (WeakReference reference in handlers) + { + EventHandler handler = reference.Target as EventHandler; + handler?.Invoke(this, EventArgs.Empty); + } + } + + [DebuggerStepThrough] + private void PurgeWeakHandlers() + { + if (weakHandlers == null) return; + + for (int i = weakHandlers.Count - 1; i >= 0; i--) + { + if (!weakHandlers[i].IsAlive) + { + weakHandlers.RemoveAt(i); + } + } + + if (weakHandlers.Count == 0) + weakHandlers = null; + } + + #endregion CanExecuteChanged event + + #region ICommand methods + + /// + /// Defines the method that determines whether the command can execute in its current state. + /// + /// Data used by the command. If the command does not require data to be passed, this object can be set to null. + /// true if this command can be executed; otherwise, false. + [DebuggerStepThrough] + public bool CanExecute(object parameter) => isEnabled ?? canExecute?.Invoke(parameter) ?? true; + + /// + /// Convenience method that invokes CanExecute without parameters. + /// + /// true if this command can be executed; otherwise, false. + [DebuggerStepThrough] + public bool CanExecute() => CanExecute(null); + + /// + /// Defines the method to be called when the command is invoked. + /// + /// Data used by the command. If the command does not require data to be passed, this object can be set to null. + /// The method returns false. + [DebuggerStepThrough] + public void Execute(object parameter) + { + if (!CanExecute(parameter)) + { + throw new InvalidOperationException("The command cannot be executed because CanExecute returned false."); + } + execute(parameter); + } + + /// + /// Convenience method that invokes the command without parameters. + /// + /// The method returns false. + [DebuggerStepThrough] + public void Execute() => Execute(null); + + /// + /// Invokes the command if the method returns true. + /// + /// Data used by the command. If the command does not require data to be passed, this object can be set to null. + /// true if this command was executed; otherwise, false. + public bool TryExecute(object parameter) + { + if (CanExecute(parameter)) + { + Execute(parameter); + return true; + } + return false; + } + + /// + /// Convenience method that invokes the command without parameters if the + /// method returns true. + /// + /// true if this command was executed; otherwise, false. + [DebuggerStepThrough] + public bool TryExecute() => TryExecute(null); + + #endregion ICommand methods + + #region Enabled state + + /// + /// Gets or sets a value indicating whether the current DelegateCommand is enabled. If this + /// value is not null, it takes precedence over the canExecute function was passed in the + /// constructor. If no function was passed and this value is null the command is enabled. + /// + public bool? IsEnabled + { + [DebuggerStepThrough] + get + { + return isEnabled; + } + [DebuggerStepThrough] + set + { + if (value != isEnabled) + { + isEnabled = value; + RaiseCanExecuteChanged(); + } + } + } + + #endregion Enabled state + } +} \ No newline at end of file diff --git a/MVVM/ViewModelBase.cs b/MVVM/ViewModelBase.cs new file mode 100644 index 0000000..42d3844 --- /dev/null +++ b/MVVM/ViewModelBase.cs @@ -0,0 +1,46 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace ViewModel +{ + public abstract class ViewModelBase : INotifyPropertyChanged + { + /// + /// Multicast event for property change notifications. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Checks if a property already matches a desired value. Sets the property and + /// notifies listeners only when necessary. + /// + /// Type of the property. + /// Reference to a property with both getter and setter. + /// Desired value for the property. + /// Name of the property used to notify listeners.This + /// value is optional and can be provided automatically when invoked from compilers that + /// support CallerMemberName. + /// True if the value was changed, false if the existing value matched the + /// desired value. + protected virtual bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null) + { + if (Equals(storage, value)) + return false; + storage = value; + // Log.DebugFormat("{0}.{1} = {2}", this.GetType().Name, propertyName, storage); + OnPropertyChanged(propertyName); + return true; + } + + /// + /// Notifies listeners that a property value has changed. + /// + /// Name of the property used to notify listeners. This + /// value is optional and can be provided automatically when invoked from compilers + /// that support . + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} \ No newline at end of file