Add MVVM Base

Add DelegateCommand
Add ViewModelBase
This commit is contained in:
筱傑 2019-08-02 14:26:44 +08:00 committed by GitHub
parent f856080ba3
commit 15b87f4685
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 343 additions and 0 deletions

297
MVVM/DelegateCommand.cs Normal file
View File

@ -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
{
/// <summary>
/// Provides an <see cref="ICommand"/> implementation which relays the Execute and CanExecute
/// method to the specified delegates.
/// </summary>
public class DelegateCommand : ICommand
{
#region Static disabled command
/// <summary>
/// A <see cref="DelegateCommand"/> instance that does nothing and can never be executed.
/// </summary>
public static readonly DelegateCommand Disabled = new DelegateCommand(() => { }) { IsEnabled = false };
#endregion Static disabled command
#region Private data
private readonly Action<object> execute;
private readonly Func<object, bool> canExecute;
private List<WeakReference> weakHandlers;
private bool? isEnabled;
private bool raiseCanExecuteChangedPending;
#endregion Private data
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="DelegateCommand"/> class.
/// </summary>
/// <param name="execute">Delegate to execute when Execute is called on the command.</param>
/// <exception cref="ArgumentNullException">The execute argument must not be null.</exception>
public DelegateCommand(Action execute)
: this(execute != null ? p => execute() : (Action<object>)null, (Func<object, bool>)null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DelegateCommand"/> class.
/// </summary>
/// <param name="execute">Delegate to execute when Execute is called on the command.</param>
/// <param name="canExecute">Delegate to execute when CanExecute is called on the command.</param>
/// <exception cref="ArgumentNullException">The execute argument must not be null.</exception>
public DelegateCommand(Action execute, Func<bool> canExecute)
: this(execute != null ? p => execute() : (Action<object>)null, canExecute != null ? p => canExecute() : (Func<object, bool>)null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DelegateCommand"/> class.
/// </summary>
/// <param name="execute">Delegate to execute when Execute is called on the command.</param>
/// <param name="canExecute">Delegate to execute when CanExecute is called on the command.</param>
/// <exception cref="ArgumentNullException">The execute argument must not be null.</exception>
public DelegateCommand(Action execute, Func<object, bool> canExecute)
: this(execute != null ? p => execute() : (Action<object>)null, canExecute)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DelegateCommand"/> class.
/// </summary>
/// <param name="execute">Delegate to execute when Execute is called on the command.</param>
/// <exception cref="ArgumentNullException">The execute argument must not be null.</exception>
public DelegateCommand(Action<object> execute)
: this(execute, (Func<object, bool>)null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DelegateCommand"/> class.
/// </summary>
/// <param name="execute">Delegate to execute when Execute is called on the command.</param>
/// <param name="canExecute">Delegate to execute when CanExecute is called on the command.</param>
/// <exception cref="ArgumentNullException">The execute argument must not be null.</exception>
public DelegateCommand(Action<object> execute, Func<bool> canExecute)
: this(execute, canExecute != null ? p => canExecute() : (Func<object, bool>)null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DelegateCommand"/> class.
/// </summary>
/// <param name="execute">Delegate to execute when Execute is called on the command.</param>
/// <param name="canExecute">Delegate to execute when CanExecute is called on the command.</param>
/// <exception cref="ArgumentNullException">The execute argument must not be null.</exception>
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException(nameof(execute));
this.execute = execute;
this.canExecute = canExecute;
}
#endregion Constructors
#region CanExecuteChanged event
/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute.
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (weakHandlers == null)
{
weakHandlers = new List<WeakReference>(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);
}
}
}
}
/// <summary>
/// Raises the <see cref="E:CanExecuteChanged"/> event.
/// </summary>
[DebuggerStepThrough]
public void RaiseCanExecuteChanged() => OnCanExecuteChanged();
/// <summary>
/// Raises the <see cref="E:CanExecuteChanged"/> event after all other processing has
/// finished. Multiple calls to this function before the asynchronous action has been
/// started are ignored.
/// </summary>
[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;
}
}
}
/// <summary>
/// Raises the <see cref="E:CanExecuteChanged"/> event.
/// </summary>
[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
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
/// <returns>true if this command can be executed; otherwise, false.</returns>
[DebuggerStepThrough]
public bool CanExecute(object parameter) => isEnabled ?? canExecute?.Invoke(parameter) ?? true;
/// <summary>
/// Convenience method that invokes CanExecute without parameters.
/// </summary>
/// <returns>true if this command can be executed; otherwise, false.</returns>
[DebuggerStepThrough]
public bool CanExecute() => CanExecute(null);
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
/// <exception cref="InvalidOperationException">The <see cref="CanExecute(object)"/> method returns false.</exception>
[DebuggerStepThrough]
public void Execute(object parameter)
{
if (!CanExecute(parameter))
{
throw new InvalidOperationException("The command cannot be executed because CanExecute returned false.");
}
execute(parameter);
}
/// <summary>
/// Convenience method that invokes the command without parameters.
/// </summary>
/// <exception cref="InvalidOperationException">The <see cref="CanExecute(object)"/> method returns false.</exception>
[DebuggerStepThrough]
public void Execute() => Execute(null);
/// <summary>
/// Invokes the command if the <see cref="CanExecute(object)"/> method returns true.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
/// <returns>true if this command was executed; otherwise, false.</returns>
public bool TryExecute(object parameter)
{
if (CanExecute(parameter))
{
Execute(parameter);
return true;
}
return false;
}
/// <summary>
/// Convenience method that invokes the command without parameters if the
/// <see cref="CanExecute(object)"/> method returns true.
/// </summary>
/// <returns>true if this command was executed; otherwise, false.</returns>
[DebuggerStepThrough]
public bool TryExecute() => TryExecute(null);
#endregion ICommand methods
#region Enabled state
/// <summary>
/// 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.
/// </summary>
public bool? IsEnabled
{
[DebuggerStepThrough]
get
{
return isEnabled;
}
[DebuggerStepThrough]
set
{
if (value != isEnabled)
{
isEnabled = value;
RaiseCanExecuteChanged();
}
}
}
#endregion Enabled state
}
}

46
MVVM/ViewModelBase.cs Normal file
View File

@ -0,0 +1,46 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ViewModel
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
/// <summary>
/// Multicast event for property change notifications.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Checks if a property already matches a desired value. Sets the property and
/// notifies listeners only when necessary.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to a property with both getter and setter.</param>
/// <param name="value">Desired value for the property.</param>
/// <param name="propertyName">Name of the property used to notify listeners.This
/// value is optional and can be provided automatically when invoked from compilers that
/// support CallerMemberName.</param>
/// <returns>True if the value was changed, false if the existing value matched the
/// desired value.</returns>
protected virtual bool SetProperty<T>(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;
}
/// <summary>
/// Notifies listeners that a property value has changed.
/// </summary>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers
/// that support <see cref="CallerMemberNameAttribute"/>.</param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}