Last active
April 3, 2024 14:28
-
-
Save heiswayi/215e5588fbbd712930ed3e0689d155d0 to your computer and use it in GitHub Desktop.
C# MVVM common classes in a single file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
{ | |
/// <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