C# MVVM common classes in a single file. Example of usage: https://heiswayi.github.io/2016/mvvm-common-classes-in-single-file
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Diagnostics; | |
using System.Linq.Expressions; | |
using System.Windows; | |
using System.Windows.Input; | |
namespace HeiswayiNrird.MVVM.Common | |
{ | |
/// <summary> | |
/// This is a base of ViewModel that implements INotifyPropertyChanged. | |
/// </summary> | |
public abstract class ViewModelBase : INotifyPropertyChanged | |
{ | |
/// <summary> | |
/// Multicast event for property change notifications. | |
/// </summary> | |
public event PropertyChangedEventHandler PropertyChanged; | |
/// <summary> | |
/// Notifies listeners that a property value has changed. | |
/// </summary> | |
/// <param name="propertyName">Name of the property used to notify listeners.</param> | |
protected void OnPropertyChanged(string propertyName) | |
{ | |
//PropertyChangedEventHandler handler = PropertyChanged; | |
//if (handler != null) | |
//{ | |
// handler(this, new PropertyChangedEventArgs(propertyName)); | |
//} | |
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); | |
} | |
/// <summary> | |
/// Raises this object's PropertyChanged event. | |
/// </summary> | |
/// <typeparam name="T">The type of the property that has a new value.</typeparam> | |
/// <param name="propertyExpression">A Lambda expression representing the property that has a new value.</param> | |
protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression) | |
{ | |
var memberExpr = propertyExpression.Body as MemberExpression; | |
if (memberExpr == null) | |
throw new ArgumentException("propertyExpression should represent access to a member"); | |
string memberName = memberExpr.Member.Name; | |
OnPropertyChanged(memberName); | |
} | |
} | |
/// <summary> | |
/// This class facilitates associating a key binding in XAML markup to a command | |
/// defined in a View Model by exposing a Command dependency property. | |
/// The class derives from Freezable to work around a limitation in WPF when data-binding from XAML. | |
/// </summary> | |
public class CommandReference : Freezable, ICommand | |
{ | |
public CommandReference() | |
{ | |
} | |
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandReference), new PropertyMetadata(new PropertyChangedCallback(OnCommandChanged))); | |
public ICommand Command | |
{ | |
get { return (ICommand)GetValue(CommandProperty); } | |
set { SetValue(CommandProperty, value); } | |
} | |
#region ICommand Members | |
public bool CanExecute(object parameter) | |
{ | |
if (Command != null) | |
return Command.CanExecute(parameter); | |
return false; | |
} | |
public void Execute(object parameter) | |
{ | |
Command.Execute(parameter); | |
} | |
public event EventHandler CanExecuteChanged; | |
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) | |
{ | |
CommandReference commandReference = d as CommandReference; | |
ICommand oldCommand = e.OldValue as ICommand; | |
ICommand newCommand = e.NewValue as ICommand; | |
if (oldCommand != null) | |
{ | |
oldCommand.CanExecuteChanged -= commandReference.CanExecuteChanged; | |
} | |
if (newCommand != null) | |
{ | |
newCommand.CanExecuteChanged += commandReference.CanExecuteChanged; | |
} | |
} | |
#endregion ICommand Members | |
#region Freezable | |
protected override Freezable CreateInstanceCore() | |
{ | |
throw new NotImplementedException(); | |
} | |
#endregion Freezable | |
} | |
/// <summary> | |
/// This class allows delegating the commanding logic to methods passed as parameters, | |
/// and enables a View to bind commands to objects that are not part of the element tree. | |
/// </summary> | |
public class DelegateCommand : ICommand | |
{ | |
#region Constructors | |
public DelegateCommand(Action executeMethod) | |
: this(executeMethod, null, false) | |
{ | |
} | |
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod) | |
: this(executeMethod, canExecuteMethod, false) | |
{ | |
} | |
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod, bool isAutomaticRequeryDisabled) | |
{ | |
if (executeMethod == null) | |
{ | |
throw new ArgumentNullException("executeMethod"); | |
} | |
_executeMethod = executeMethod; | |
_canExecuteMethod = canExecuteMethod; | |
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled; | |
} | |
#endregion Constructors | |
#region Public Methods | |
public bool CanExecute() | |
{ | |
if (_canExecuteMethod != null) | |
{ | |
return _canExecuteMethod(); | |
} | |
return true; | |
} | |
public void Execute() | |
{ | |
if (_executeMethod != null) | |
{ | |
_executeMethod(); | |
} | |
} | |
public bool IsAutomaticRequeryDisabled | |
{ | |
get | |
{ | |
return _isAutomaticRequeryDisabled; | |
} | |
set | |
{ | |
if (_isAutomaticRequeryDisabled != value) | |
{ | |
if (value) | |
{ | |
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers); | |
} | |
else | |
{ | |
CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers); | |
} | |
_isAutomaticRequeryDisabled = value; | |
} | |
} | |
} | |
public void RaiseCanExecuteChanged() | |
{ | |
OnCanExecuteChanged(); | |
} | |
protected virtual void OnCanExecuteChanged() | |
{ | |
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers); | |
} | |
#endregion Public Methods | |
#region ICommand Members | |
public event EventHandler CanExecuteChanged | |
{ | |
add | |
{ | |
if (!_isAutomaticRequeryDisabled) | |
{ | |
CommandManager.RequerySuggested += value; | |
} | |
CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2); | |
} | |
remove | |
{ | |
if (!_isAutomaticRequeryDisabled) | |
{ | |
CommandManager.RequerySuggested -= value; | |
} | |
CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value); | |
} | |
} | |
bool ICommand.CanExecute(object parameter) | |
{ | |
return CanExecute(); | |
} | |
void ICommand.Execute(object parameter) | |
{ | |
Execute(); | |
} | |
#endregion ICommand Members | |
#region Data | |
private readonly Action _executeMethod = null; | |
private readonly Func<bool> _canExecuteMethod = null; | |
private bool _isAutomaticRequeryDisabled = false; | |
private List<WeakReference> _canExecuteChangedHandlers; | |
#endregion Data | |
} | |
/// <summary> | |
/// This class allows delegating the commanding logic to methods passed as parameters, | |
/// and enables a View to bind commands to objects that are not part of the element tree. | |
/// </summary> | |
/// <typeparam name="T">Type of the parameter passed to the delegates.</typeparam> | |
public class DelegateCommand<T> : ICommand | |
{ | |
#region Constructors | |
public DelegateCommand(Action<T> executeMethod) | |
: this(executeMethod, null, false) | |
{ | |
} | |
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod) | |
: this(executeMethod, canExecuteMethod, false) | |
{ | |
} | |
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod, bool isAutomaticRequeryDisabled) | |
{ | |
if (executeMethod == null) | |
{ | |
throw new ArgumentNullException("executeMethod"); | |
} | |
_executeMethod = executeMethod; | |
_canExecuteMethod = canExecuteMethod; | |
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled; | |
} | |
#endregion Constructors | |
#region Public Methods | |
public bool CanExecute(T parameter) | |
{ | |
if (_canExecuteMethod != null) | |
{ | |
return _canExecuteMethod(parameter); | |
} | |
return true; | |
} | |
public void Execute(T parameter) | |
{ | |
if (_executeMethod != null) | |
{ | |
_executeMethod(parameter); | |
} | |
} | |
public void RaiseCanExecuteChanged() | |
{ | |
OnCanExecuteChanged(); | |
} | |
protected virtual void OnCanExecuteChanged() | |
{ | |
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers); | |
} | |
public bool IsAutomaticRequeryDisabled | |
{ | |
get | |
{ | |
return _isAutomaticRequeryDisabled; | |
} | |
set | |
{ | |
if (_isAutomaticRequeryDisabled != value) | |
{ | |
if (value) | |
{ | |
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers); | |
} | |
else | |
{ | |
CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers); | |
} | |
_isAutomaticRequeryDisabled = value; | |
} | |
} | |
} | |
#endregion Public Methods | |
#region ICommand Members | |
public event EventHandler CanExecuteChanged | |
{ | |
add | |
{ | |
if (!_isAutomaticRequeryDisabled) | |
{ | |
CommandManager.RequerySuggested += value; | |
} | |
CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2); | |
} | |
remove | |
{ | |
if (!_isAutomaticRequeryDisabled) | |
{ | |
CommandManager.RequerySuggested -= value; | |
} | |
CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value); | |
} | |
} | |
bool ICommand.CanExecute(object parameter) | |
{ | |
if (parameter == null && | |
typeof(T).IsValueType) | |
{ | |
return (_canExecuteMethod == null); | |
} | |
return CanExecute((T)parameter); | |
} | |
void ICommand.Execute(object parameter) | |
{ | |
Execute((T)parameter); | |
} | |
#endregion ICommand Members | |
#region Data | |
private readonly Action<T> _executeMethod = null; | |
private readonly Func<T, bool> _canExecuteMethod = null; | |
private bool _isAutomaticRequeryDisabled = false; | |
private List<WeakReference> _canExecuteChangedHandlers; | |
#endregion Data | |
} | |
/// <summary> | |
/// This class contains methods for the CommandManager that | |
/// help avoid memory leaks by using weak references. | |
/// </summary> | |
internal class CommandManagerHelper | |
{ | |
internal static void CallWeakReferenceHandlers(List<WeakReference> handlers) | |
{ | |
if (handlers != null) | |
{ | |
EventHandler[] callees = new EventHandler[handlers.Count]; | |
int count = 0; | |
for (int i = handlers.Count - 1; i >= 0; i--) | |
{ | |
WeakReference reference = handlers[i]; | |
EventHandler handler = reference.Target as EventHandler; | |
if (handler == null) | |
{ | |
handlers.RemoveAt(i); | |
} | |
else | |
{ | |
callees[count] = handler; | |
count++; | |
} | |
} | |
for (int i = 0; i < count; i++) | |
{ | |
EventHandler handler = callees[i]; | |
handler(null, EventArgs.Empty); | |
} | |
} | |
} | |
internal static void AddHandlersToRequerySuggested(List<WeakReference> handlers) | |
{ | |
if (handlers != null) | |
{ | |
foreach (WeakReference handlerRef in handlers) | |
{ | |
EventHandler handler = handlerRef.Target as EventHandler; | |
if (handler != null) | |
{ | |
CommandManager.RequerySuggested += handler; | |
} | |
} | |
} | |
} | |
internal static void RemoveHandlersFromRequerySuggested(List<WeakReference> handlers) | |
{ | |
if (handlers != null) | |
{ | |
foreach (WeakReference handlerRef in handlers) | |
{ | |
EventHandler handler = handlerRef.Target as EventHandler; | |
if (handler != null) | |
{ | |
CommandManager.RequerySuggested -= handler; | |
} | |
} | |
} | |
} | |
internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler) | |
{ | |
AddWeakReferenceHandler(ref handlers, handler, -1); | |
} | |
internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler, int defaultListSize) | |
{ | |
if (handlers == null) | |
{ | |
handlers = (defaultListSize > 0 ? new List<WeakReference>(defaultListSize) : new List<WeakReference>()); | |
} | |
handlers.Add(new WeakReference(handler)); | |
} | |
internal static void RemoveWeakReferenceHandler(List<WeakReference> handlers, EventHandler handler) | |
{ | |
if (handlers != null) | |
{ | |
for (int i = handlers.Count - 1; i >= 0; i--) | |
{ | |
WeakReference reference = handlers[i]; | |
EventHandler existingHandler = reference.Target as EventHandler; | |
if ((existingHandler == null) || (existingHandler == handler)) | |
{ | |
handlers.RemoveAt(i); | |
} | |
} | |
} | |
} | |
} | |
/// <summary> | |
/// A command whose sole purpose is to relay its functionality to other objects by invoking delegates. | |
/// The default return value for the CanExecute method is 'true'. | |
/// </summary> | |
public class RelayCommand : ICommand | |
{ | |
#region Fields | |
private readonly Action<object> _execute; | |
private readonly Predicate<object> _canExecute; | |
#endregion Fields | |
#region Constructors | |
public RelayCommand(Action<object> execute) | |
: this(execute, null) | |
{ | |
} | |
public RelayCommand(Action<object> execute, Predicate<object> canExecute) | |
{ | |
if (execute == null) | |
throw new ArgumentNullException("execute"); | |
_execute = execute; | |
_canExecute = canExecute; | |
} | |
#endregion Constructors | |
#region ICommand Members | |
[DebuggerStepThrough] | |
public bool CanExecute(object parameter) | |
{ | |
return _canExecute == null ? true : _canExecute(parameter); | |
} | |
public event EventHandler CanExecuteChanged | |
{ | |
add { CommandManager.RequerySuggested += value; } | |
remove { CommandManager.RequerySuggested -= value; } | |
} | |
public void Execute(object parameter) | |
{ | |
_execute(parameter); | |
} | |
#endregion ICommand Members | |
} | |
/// <summary> | |
/// A command whose sole purpose is to relay its functionality to other objects by invoking delegates. | |
/// The default return value for the CanExecute method is 'true'. | |
/// </summary> | |
/// <typeparam name="T">Type of the parameter passed to the delegates.</typeparam> | |
public class RelayCommand<T> : ICommand | |
{ | |
#region Fields | |
private readonly Action<T> _execute = null; | |
private readonly Predicate<T> _canExecute = null; | |
#endregion Fields | |
#region Constructors | |
/// <summary> | |
/// Initializes a new instance of <see cref="DelegateCommand{T}"/>. | |
/// </summary> | |
/// <param name="execute">Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.</param> | |
/// <remarks><seealso cref="CanExecute"/> will always return true.</remarks> | |
public RelayCommand(Action<T> execute) | |
: this(execute, null) | |
{ | |
} | |
/// <summary> | |
/// Creates a new command. | |
/// </summary> | |
/// <param name="execute">The execution logic.</param> | |
/// <param name="canExecute">The execution status logic.</param> | |
public RelayCommand(Action<T> execute, Predicate<T> canExecute) | |
{ | |
if (execute == null) | |
throw new ArgumentNullException("execute"); | |
_execute = execute; | |
_canExecute = canExecute; | |
} | |
#endregion Constructors | |
#region ICommand Members | |
/// <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> | |
public bool CanExecute(object parameter) | |
{ | |
return _canExecute == null ? true : _canExecute((T)parameter); | |
} | |
/// <summary> | |
/// Occurs when changes occur that affect whether or not the command should execute. | |
/// </summary> | |
public event EventHandler CanExecuteChanged | |
{ | |
add { CommandManager.RequerySuggested += value; } | |
remove { CommandManager.RequerySuggested -= value; } | |
} | |
/// <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 <see langword="null" />.</param> | |
public void Execute(object parameter) | |
{ | |
_execute((T)parameter); | |
} | |
#endregion ICommand Members | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment