├── README.md ├── Source ├── Common │ ├── Services │ │ ├── IService1.cs │ │ ├── IService2.cs │ │ ├── IService3.cs │ │ ├── Service1.cs │ │ ├── Service2.cs │ │ └── Service3.cs │ ├── Common.csproj │ ├── ICloneableT.cs │ ├── IEventAggregator.cs │ ├── EventAggregator.cs │ ├── DelegateRule.cs │ ├── Commands │ │ ├── Command.cs │ │ ├── AsyncCommand.cs │ │ ├── CommandT.cs │ │ ├── AsyncCommandT.cs │ │ ├── CommandBase.cs │ │ ├── DelegateCommand.cs │ │ ├── AsyncDelegateCommand.cs │ │ ├── DelegateCommandT.cs │ │ └── AsyncDelegateCommandT.cs │ ├── Rule.cs │ ├── RuleCollection.cs │ ├── Disposable.cs │ ├── RevertibleChangeTracking.cs │ ├── EditableObject.cs │ ├── NotifyDataErrorInfo.cs │ └── NotifyPropertyChanges.cs ├── Rx │ ├── Rx.csproj │ └── Program.cs ├── CommandPattern │ ├── CommandPattern.csproj │ └── Program.cs ├── EventAggregatorPattern │ ├── EventAggregatorPattern.csproj │ └── Program.cs └── ViewModelComposition │ ├── ViewModelComposition.csproj │ └── Program.cs ├── LICENSE ├── .gitattributes ├── MVVM-Design-Patterns.sln ├── .gitignore └── .editorconfig /README.md: -------------------------------------------------------------------------------- 1 | # MVVM-Design-Patterns 2 | Showcasing the design patterns you can use alongside Model-View-ViewModel (MVVM) 3 | -------------------------------------------------------------------------------- /Source/Common/Services/IService1.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Services 2 | { 3 | public interface IService1 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Source/Common/Services/IService2.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Services 2 | { 3 | public interface IService2 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Source/Common/Services/IService3.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Services 2 | { 3 | public interface IService3 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Source/Common/Services/Service1.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Services 2 | { 3 | public class Service1 : IService1 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Source/Common/Services/Service2.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Services 2 | { 3 | public class Service2 : IService2 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Source/Common/Services/Service3.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Services 2 | { 3 | public class Service3 : IService3 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Source/Common/Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Source/Rx/Rx.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Source/CommandPattern/CommandPattern.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Source/EventAggregatorPattern/EventAggregatorPattern.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Source/ViewModelComposition/ViewModelComposition.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Source/Common/ICloneableT.cs: -------------------------------------------------------------------------------- 1 | namespace Common 2 | { 3 | using System; 4 | 5 | /// 6 | /// Produces deep clones of objects. 7 | /// 8 | /// The type of the object to clone. 9 | public interface ICloneable : ICloneable 10 | { 11 | /// 12 | /// Clones the clone-able object of type . 13 | /// 14 | /// 15 | /// The cloned object of type . 16 | /// 17 | new T Clone(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Source/Common/IEventAggregator.cs: -------------------------------------------------------------------------------- 1 | namespace Common 2 | { 3 | using System; 4 | 5 | /// 6 | /// Channels events from multiple objects into a single object to simplify registration for clients. 7 | /// 8 | /// See https://www.martinfowler.com/eaaDev/EventAggregator.html. 9 | public interface IEventAggregator 10 | { 11 | /// 12 | /// Gets the event with the specified type. 13 | /// 14 | /// The type of the event. 15 | /// An . 16 | IObservable GetEvent(); 17 | 18 | /// 19 | /// Publishes the specified event. 20 | /// 21 | /// The type of the event. 22 | /// The event to publish. 23 | void Publish(TEvent eventValue); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Muhammad Rehan Saeed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Source/Common/EventAggregator.cs: -------------------------------------------------------------------------------- 1 | namespace Common 2 | { 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Reactive.Subjects; 6 | 7 | /// 8 | public class EventAggregator : Disposable, IEventAggregator 9 | { 10 | private readonly ConcurrentDictionary subjects = new ConcurrentDictionary(); 11 | 12 | /// 13 | public IObservable GetEvent() => 14 | (ISubject)this.subjects.GetOrAdd(typeof(TEvent), x => new Subject()); 15 | 16 | /// 17 | public void Publish(TEvent eventValue) 18 | { 19 | if (this.subjects.TryGetValue(typeof(TEvent), out var subject)) 20 | { 21 | ((ISubject)subject).OnNext(eventValue); 22 | } 23 | } 24 | 25 | /// 26 | protected override void DisposeManaged() 27 | { 28 | foreach (var item in this.subjects) 29 | { 30 | if (item.Value is IDisposable disposable) 31 | { 32 | disposable.Dispose(); 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Git Line Endings # 3 | ############################### 4 | 5 | # Set default behavior to automatically normalize line endings. 6 | * text=auto 7 | 8 | # Force batch scripts to always use CRLF line endings so that if a repo is accessed 9 | # in Windows via a file share from Linux, the scripts will work. 10 | *.{cmd,[cC][mM][dD]} text eol=crlf 11 | *.{bat,[bB][aA][tT]} text eol=crlf 12 | 13 | # Force bash scripts to always use LF line endings so that if a repo is accessed 14 | # in Unix via a file share from Windows, the scripts will work. 15 | *.sh text eol=lf 16 | 17 | ############################### 18 | # Git Large File System (LFS) # 19 | ############################### 20 | 21 | # Archives 22 | *.7z filter=lfs diff=lfs merge=lfs -text 23 | *.br filter=lfs diff=lfs merge=lfs -text 24 | *.gz filter=lfs diff=lfs merge=lfs -text 25 | *.tar filter=lfs diff=lfs merge=lfs -text 26 | *.zip filter=lfs diff=lfs merge=lfs -text 27 | 28 | # Documents 29 | *.pdf filter=lfs diff=lfs merge=lfs -text 30 | 31 | # Images 32 | *.gif filter=lfs diff=lfs merge=lfs -text 33 | *.ico filter=lfs diff=lfs merge=lfs -text 34 | *.jpg filter=lfs diff=lfs merge=lfs -text 35 | *.png filter=lfs diff=lfs merge=lfs -text 36 | *.psd filter=lfs diff=lfs merge=lfs -text 37 | *.webp filter=lfs diff=lfs merge=lfs -text 38 | 39 | # Fonts 40 | *.woff2 filter=lfs diff=lfs merge=lfs -text 41 | 42 | # Other 43 | *.exe filter=lfs diff=lfs merge=lfs -text 44 | -------------------------------------------------------------------------------- /Source/Common/DelegateRule.cs: -------------------------------------------------------------------------------- 1 | namespace Common 2 | { 3 | using System; 4 | 5 | /// 6 | /// Determines whether or not an object of type satisfies a rule and 7 | /// provides an error if it does not. 8 | /// 9 | /// The type of the object the rule can be applied to. 10 | public sealed class DelegateRule : Rule 11 | { 12 | private readonly Func rule; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// >The name of the property the rules applies to. 18 | /// The error if the rules fails. 19 | /// The rule to execute. 20 | public DelegateRule(string propertyName, object error, Func rule) 21 | : base(propertyName, error) => 22 | this.rule = rule ?? throw new ArgumentNullException(nameof(rule)); 23 | 24 | /// 25 | /// Applies the rule to the specified object. 26 | /// 27 | /// The object to apply the rule to. 28 | /// 29 | /// true if the object satisfies the rule, otherwise false. 30 | /// 31 | public override bool Apply(T obj) => this.rule(obj); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Source/Common/Commands/Command.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Commands 2 | { 3 | using System.Windows.Input; 4 | 5 | /// 6 | /// base class. 7 | /// 8 | public abstract class Command : CommandBase 9 | { 10 | /// 11 | /// Defines the method that determines whether the command can execute in its current state. 12 | /// 13 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 14 | /// 15 | /// true if this command can be executed; otherwise, false. 16 | /// 17 | public override bool CanExecute(object parameter) => this.CanExecute(); 18 | 19 | /// 20 | /// Defines the method to be called when the command is invoked. 21 | /// 22 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 23 | public override void Execute(object parameter) => this.Execute(); 24 | 25 | /// 26 | /// Determines whether this instance can execute. 27 | /// 28 | /// 29 | /// true if this instance can execute; otherwise, false. 30 | /// 31 | public virtual bool CanExecute() => true; 32 | 33 | /// 34 | /// Executes this instance. 35 | /// 36 | public abstract void Execute(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Source/Common/Commands/AsyncCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Commands 2 | { 3 | using System.Threading.Tasks; 4 | using System.Windows.Input; 5 | 6 | /// 7 | /// base class. 8 | /// 9 | public abstract class AsyncCommand : CommandBase 10 | { 11 | /// 12 | /// Defines the method that determines whether the command can execute in its current state. 13 | /// 14 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 15 | /// 16 | /// true if this command can be executed; otherwise, false. 17 | /// 18 | public override bool CanExecute(object parameter) => this.CanExecute(); 19 | 20 | /// 21 | /// Defines the method to be called when the command is invoked. 22 | /// 23 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 24 | public override void Execute(object parameter) => this.Execute(); 25 | 26 | /// 27 | /// Determines whether this instance can execute. 28 | /// 29 | /// 30 | /// true if this instance can execute; otherwise, false. 31 | /// 32 | public virtual bool CanExecute() => true; 33 | 34 | /// 35 | /// Executes this instance. 36 | /// 37 | public abstract Task Execute(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Source/Common/Rule.cs: -------------------------------------------------------------------------------- 1 | namespace Common 2 | { 3 | using System; 4 | 5 | /// 6 | /// A named rule containing an error to be used if the rule fails. 7 | /// 8 | /// The type of the object the rule applies to. 9 | public abstract class Rule 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The name of the property this instance applies to. 15 | /// The error message if the rules fails. 16 | protected Rule(string propertyName, object error) 17 | { 18 | this.PropertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName)); 19 | this.Error = error ?? throw new ArgumentNullException(nameof(error)); 20 | } 21 | 22 | /// 23 | /// Gets the name of the property this instance applies to. 24 | /// 25 | /// The name of the property this instance applies to. 26 | public string PropertyName { get; } 27 | 28 | /// 29 | /// Gets the error message if the rules fails. 30 | /// 31 | /// The error message if the rules fails. 32 | public object Error { get; } 33 | 34 | /// 35 | /// Applies the rule to the specified object. 36 | /// 37 | /// The object to apply the rule to. 38 | /// 39 | /// true if the object satisfies the rule, otherwise false. 40 | /// 41 | public abstract bool Apply(T obj); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Source/Common/Commands/CommandT.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Commands 2 | { 3 | using System.Windows.Input; 4 | 5 | /// 6 | /// base class. 7 | /// 8 | /// The type of the command parameter. 9 | public abstract class Command : CommandBase 10 | { 11 | /// 12 | /// Defines the method that determines whether the command can execute in its current state. 13 | /// 14 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 15 | /// 16 | /// true if this command can be executed; otherwise, false. 17 | /// 18 | public override bool CanExecute(object parameter) => this.CanExecute((T)parameter); 19 | 20 | /// 21 | /// Defines the method to be called when the command is invoked. 22 | /// 23 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 24 | public override void Execute(object parameter) => this.Execute((T)parameter); 25 | 26 | /// 27 | /// Determines whether this instance can execute. 28 | /// 29 | /// The command parameter. 30 | /// 31 | /// true if this instance can execute; otherwise, false. 32 | /// 33 | public virtual bool CanExecute(T parameter) => true; 34 | 35 | /// 36 | /// Executes this instance. 37 | /// 38 | /// The command parameter. 39 | public abstract void Execute(T parameter); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Source/Common/Commands/AsyncCommandT.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Commands 2 | { 3 | using System.Threading.Tasks; 4 | using System.Windows.Input; 5 | 6 | /// 7 | /// base class. 8 | /// 9 | /// The type of the command parameter. 10 | public abstract class AsyncCommand : CommandBase 11 | { 12 | /// 13 | /// Defines the method that determines whether the command can execute in its current state. 14 | /// 15 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 16 | /// 17 | /// true if this command can be executed; otherwise, false. 18 | /// 19 | public override bool CanExecute(object parameter) => this.CanExecute((T)parameter); 20 | 21 | /// 22 | /// Defines the method to be called when the command is invoked. 23 | /// 24 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 25 | public override void Execute(object parameter) => this.Execute((T)parameter); 26 | 27 | /// 28 | /// Determines whether this instance can execute. 29 | /// 30 | /// The command parameter. 31 | /// 32 | /// true if this instance can execute; otherwise, false. 33 | /// 34 | public virtual bool CanExecute(T parameter) => true; 35 | 36 | /// 37 | /// Executes this instance. 38 | /// 39 | /// The command parameter. 40 | public abstract Task Execute(T parameter); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Source/Common/RuleCollection.cs: -------------------------------------------------------------------------------- 1 | namespace Common 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | 7 | /// 8 | /// A collection of rules. 9 | /// 10 | /// The type of the object the rules can be applied to. 11 | public sealed class RuleCollection : Collection> 12 | { 13 | /// 14 | /// Adds a new to this instance. 15 | /// 16 | /// The name of the property the rules applies to. 17 | /// The error if the object does not satisfy the rule. 18 | /// The rule to execute. 19 | public void Add(string propertyName, object error, Func rule) => 20 | this.Add(new DelegateRule(propertyName, error, rule)); 21 | 22 | /// 23 | /// Applies the 's contained in this instance to . 24 | /// 25 | /// The object to apply the rules to. 26 | /// Name of the property we want to apply rules for. null 27 | /// to apply all rules. 28 | /// A collection of errors. 29 | public IEnumerable Apply(T obj, string propertyName) 30 | { 31 | var errors = new List(); 32 | 33 | foreach (var rule in this) 34 | { 35 | if (string.IsNullOrEmpty(propertyName) || rule.PropertyName.Equals(propertyName)) 36 | { 37 | if (!rule.Apply(obj)) 38 | { 39 | errors.Add(rule.Error); 40 | } 41 | } 42 | } 43 | 44 | return errors; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Source/Common/Commands/CommandBase.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Commands 2 | { 3 | using System; 4 | using System.Windows.Input; 5 | 6 | /// 7 | /// base class. 8 | /// 9 | public abstract class CommandBase : ICommand 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | protected CommandBase() 15 | { 16 | } 17 | 18 | /// 19 | /// Occurs when changes occur that affect whether or not the command should execute. 20 | /// 21 | public event EventHandler CanExecuteChanged; 22 | 23 | /// 24 | /// Defines the method that determines whether the command can execute in its current state. 25 | /// 26 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 27 | /// 28 | /// true if this command can be executed; otherwise, false. 29 | /// 30 | public abstract bool CanExecute(object parameter); 31 | 32 | /// 33 | /// Defines the method to be called when the command is invoked. 34 | /// 35 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 36 | public abstract void Execute(object parameter); 37 | 38 | /// 39 | /// Raises the can execute changed event. 40 | /// 41 | public void RaiseCanExecuteChanged() => this.OnCanExecuteChanged(); 42 | 43 | /// 44 | /// Called when can execute is changed. 45 | /// 46 | protected virtual void OnCanExecuteChanged() => this.CanExecuteChanged?.Invoke(this, EventArgs.Empty); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Source/Common/Commands/DelegateCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Commands 2 | { 3 | using System; 4 | 5 | /// 6 | /// This class allows delegating the commanding logic to methods passed as parameters, 7 | /// and enables a View to bind commands to objects that are not part of the element tree. 8 | /// 9 | public sealed class DelegateCommand : Command 10 | { 11 | private readonly Action execute; 12 | private readonly Func canExecute; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The execute. 18 | public DelegateCommand(Action execute) 19 | : this(execute, null) 20 | { 21 | } 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// The execute. 27 | /// The can execute. 28 | public DelegateCommand(Action execute, Func canExecute) 29 | { 30 | this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); 31 | this.canExecute = canExecute; 32 | } 33 | 34 | /// 35 | /// Determines whether this instance can execute. 36 | /// 37 | /// 38 | /// true if this instance can execute; otherwise, false. 39 | /// 40 | public override bool CanExecute() 41 | { 42 | if (this.canExecute != null) 43 | { 44 | return this.canExecute(); 45 | } 46 | 47 | return true; 48 | } 49 | 50 | /// 51 | /// Executes this instance. 52 | /// 53 | public override void Execute() => this.execute.Invoke(); 54 | } 55 | } -------------------------------------------------------------------------------- /Source/Common/Commands/AsyncDelegateCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Commands 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | /// 7 | /// This class allows delegating the commanding logic to methods passed as parameters, 8 | /// and enables a View to bind commands to objects that are not part of the element tree. 9 | /// 10 | public sealed class AsyncDelegateCommand : AsyncCommand 11 | { 12 | private readonly Func execute; 13 | private readonly Func canExecute; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The execute. 19 | public AsyncDelegateCommand(Func execute) 20 | : this(execute, null) 21 | { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The execute. 28 | /// The can execute. 29 | public AsyncDelegateCommand(Func execute, Func canExecute) 30 | { 31 | this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); 32 | this.canExecute = canExecute; 33 | } 34 | 35 | /// 36 | /// Determines whether this instance can execute. 37 | /// 38 | /// 39 | /// true if this instance can execute; otherwise, false. 40 | /// 41 | public override bool CanExecute() 42 | { 43 | if (this.canExecute != null) 44 | { 45 | return this.canExecute(); 46 | } 47 | 48 | return true; 49 | } 50 | 51 | /// 52 | /// Executes this instance. 53 | /// 54 | public override Task Execute() => this.execute(); 55 | } 56 | } -------------------------------------------------------------------------------- /Source/Common/Commands/DelegateCommandT.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Commands 2 | { 3 | using System; 4 | 5 | /// 6 | /// This class allows delegating the commanding logic to methods passed as parameters, 7 | /// and enables a View to bind commands to objects that are not part of the element tree. 8 | /// 9 | /// The type of the command parameter. 10 | public sealed class DelegateCommand : Command 11 | { 12 | private readonly Action execute; 13 | private readonly Func canExecute; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The execute. 19 | public DelegateCommand(Action execute) 20 | : this(execute, null) 21 | { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The execute. 28 | /// The can execute. 29 | public DelegateCommand(Action execute, Func canExecute) 30 | { 31 | this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); 32 | this.canExecute = canExecute; 33 | } 34 | 35 | /// 36 | /// Determines whether this instance can execute. 37 | /// 38 | /// The command parameter. 39 | /// 40 | /// true if this instance can execute; otherwise, false. 41 | /// 42 | public override bool CanExecute(T parameter) 43 | { 44 | if (this.canExecute != null) 45 | { 46 | return this.canExecute(parameter); 47 | } 48 | 49 | return true; 50 | } 51 | 52 | /// 53 | /// Executes this instance. 54 | /// 55 | /// The command parameter. 56 | public override void Execute(T parameter) => this.execute.Invoke(parameter); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Source/EventAggregatorPattern/Program.cs: -------------------------------------------------------------------------------- 1 | namespace EventAggregatorPattern 2 | { 3 | using System; 4 | using Common; 5 | using DryIoc; 6 | 7 | public class Program 8 | { 9 | public static void Main() 10 | { 11 | var container = new Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient()); 12 | container.Register(Reuse.Singleton); 13 | container.Register(); 14 | container.Register(); 15 | 16 | var subscriberViewModel = container.Resolve(); 17 | var publisherViewModel = container.Resolve(); 18 | publisherViewModel.NewMessage("Hello"); 19 | 20 | Console.Read(); 21 | } 22 | } 23 | 24 | public class NewMessageEvent 25 | { 26 | public NewMessageEvent(string text) => this.Text = text; 27 | 28 | public string Text { get; } 29 | } 30 | 31 | public class PublisherViewModel 32 | { 33 | private readonly IEventAggregator eventAggregator; 34 | 35 | public PublisherViewModel(IEventAggregator eventAggregator) => this.eventAggregator = eventAggregator; 36 | 37 | public void NewMessage(string text) => this.eventAggregator.Publish(new NewMessageEvent(text)); 38 | } 39 | 40 | public class SubscriberViewModel : Disposable 41 | { 42 | private readonly IDisposable newMessageSubscription; 43 | 44 | public SubscriberViewModel(IEventAggregator eventAggregator) => 45 | this.newMessageSubscription = eventAggregator 46 | .GetEvent() 47 | // .ObserveOnDispatcher() 48 | .Subscribe(this.OnNewMessage); 49 | 50 | protected override void DisposeManaged() => this.newMessageSubscription.Dispose(); 51 | 52 | private void OnNewMessage(NewMessageEvent newMessageEvent) => Console.WriteLine(newMessageEvent.Text); 53 | } 54 | 55 | // Publisher and subscriber are completely decoupled. 56 | // There can be multiple publishers e.g. Clear conversation history from two totally different parts of the app. 57 | // There can be multiple subscribers e.g. New user message event kicks off multiple view models. 58 | } 59 | -------------------------------------------------------------------------------- /Source/Common/Commands/AsyncDelegateCommandT.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Commands 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | /// 7 | /// This class allows delegating the commanding logic to methods passed as parameters, 8 | /// and enables a View to bind commands to objects that are not part of the element tree. 9 | /// 10 | /// The type of the command parameter. 11 | public sealed class AsyncDelegateCommand : AsyncCommand 12 | { 13 | private readonly Func execute; 14 | private readonly Func canExecute; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The execute. 20 | public AsyncDelegateCommand(Func execute) 21 | : this(execute, null) 22 | { 23 | } 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// The execute. 29 | /// The can execute. 30 | public AsyncDelegateCommand(Func execute, Func canExecute) 31 | { 32 | this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); 33 | this.canExecute = canExecute; 34 | } 35 | 36 | /// 37 | /// Determines whether this instance can execute. 38 | /// 39 | /// The command parameter. 40 | /// 41 | /// true if this instance can execute; otherwise, false. 42 | /// 43 | public override bool CanExecute(T parameter) 44 | { 45 | if (this.canExecute != null) 46 | { 47 | return this.canExecute(parameter); 48 | } 49 | 50 | return true; 51 | } 52 | 53 | /// 54 | /// Executes this instance. 55 | /// 56 | /// The command parameter. 57 | public override Task Execute(T parameter) => this.execute(parameter); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Source/ViewModelComposition/Program.cs: -------------------------------------------------------------------------------- 1 | namespace ViewModelComposition 2 | { 3 | using System; 4 | using System.Collections.ObjectModel; 5 | using Common; 6 | using Common.Services; 7 | using DryIoc; 8 | 9 | public class Program 10 | { 11 | public static void Main() 12 | { 13 | var container = new Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient()); 14 | container.Register(); 15 | container.Register(); 16 | container.Register(); 17 | container.Register(); 18 | container.Register(); 19 | container.Register(); 20 | 21 | var conversationViewModel = container.Resolve(); 22 | conversationViewModel.AddMessage("Hello World"); 23 | 24 | Console.Read(); 25 | } 26 | } 27 | 28 | public class ConversationViewModel : NotifyPropertyChanges 29 | { 30 | private readonly Func messageViewModelFactory; 31 | private readonly IService1 service1; 32 | 33 | public ConversationViewModel( 34 | InputViewModel inputViewModel, 35 | Func messageViewModelFactory, 36 | IService1 service1) 37 | { 38 | this.Input = inputViewModel; 39 | this.messageViewModelFactory = messageViewModelFactory; 40 | this.service1 = service1; 41 | 42 | this.Messages = new ObservableCollection(); 43 | } 44 | 45 | public InputViewModel Input { get; } 46 | 47 | public ObservableCollection Messages { get; } 48 | 49 | public void AddMessage(string text) 50 | { 51 | var messageViewModel = this.messageViewModelFactory(); 52 | messageViewModel.Text = text; 53 | this.Messages.Add(messageViewModel); 54 | } 55 | } 56 | 57 | public class InputViewModel : NotifyPropertyChanges 58 | { 59 | private readonly IService2 service2; 60 | 61 | private string text; 62 | 63 | public InputViewModel(IService2 service2) => this.service2 = service2; 64 | 65 | public string Text 66 | { 67 | get => this.text; 68 | set => this.SetProperty(ref this.text, value); 69 | } 70 | } 71 | 72 | public class MessageViewModel : NotifyPropertyChanges 73 | { 74 | private readonly IService3 service3; 75 | 76 | private string text; 77 | 78 | public MessageViewModel(IService3 service3) => this.service3 = service3; 79 | 80 | public string Text 81 | { 82 | get => this.text; 83 | set => this.SetProperty(ref this.text, value); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Source/Common/Disposable.cs: -------------------------------------------------------------------------------- 1 | namespace Common 2 | { 3 | using System; 4 | 5 | /// 6 | /// Base class for members implementing . 7 | /// 8 | public abstract class Disposable : IDisposable 9 | { 10 | /// 11 | /// Finalizes an instance of the class. Releases unmanaged 12 | /// resources and performs other clean-up operations before the 13 | /// is reclaimed by garbage collection. Will run only if the 14 | /// Dispose method does not get called. 15 | /// 16 | ~Disposable() 17 | { 18 | this.Dispose(false); 19 | } 20 | 21 | /// 22 | /// Gets a value indicating whether this is disposed. 23 | /// 24 | /// true if disposed; otherwise, false. 25 | public bool IsDisposed { get; private set; } 26 | 27 | /// 28 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 29 | /// 30 | public void Dispose() 31 | { 32 | // Dispose all managed and unmanaged resources. 33 | this.Dispose(true); 34 | 35 | // Take this object off the finalization queue and prevent finalization code for this 36 | // object from executing a second time. 37 | GC.SuppressFinalize(this); 38 | } 39 | 40 | /// 41 | /// Disposes the managed resources implementing . 42 | /// 43 | protected virtual void DisposeManaged() 44 | { 45 | } 46 | 47 | /// 48 | /// Disposes the unmanaged resources implementing . 49 | /// 50 | protected virtual void DisposeUnmanaged() 51 | { 52 | } 53 | 54 | /// 55 | /// Throws a if this instance is disposed. 56 | /// 57 | protected void ThrowIfDisposed() 58 | { 59 | if (this.IsDisposed) 60 | { 61 | throw new ObjectDisposedException(this.GetType().Name); 62 | } 63 | } 64 | 65 | /// 66 | /// Releases unmanaged and - optionally - managed resources. 67 | /// 68 | /// true to release both managed and unmanaged resources; 69 | /// false to release only unmanaged resources, called from the finalizer only. 70 | /// We suppress CA1063 which requires that this method be protected virtual because we want to hide 71 | /// the internal implementation. 72 | #pragma warning disable CA1063 // Implement IDisposable Correctly 73 | private void Dispose(bool disposing) 74 | #pragma warning restore CA1063 // Implement IDisposable Correctly 75 | { 76 | // Check to see if Dispose has already been called. 77 | if (!this.IsDisposed) 78 | { 79 | // If disposing managed and unmanaged resources. 80 | if (disposing) 81 | { 82 | this.DisposeManaged(); 83 | } 84 | 85 | this.DisposeUnmanaged(); 86 | 87 | this.IsDisposed = true; 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /MVVM-Design-Patterns.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29209.62 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{E33EDBE9-3502-465B-A4CB-C3D4D471FCE2}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "Source\Common\Common.csproj", "{55613994-149C-4D48-A06E-069EEB0935D0}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ViewModelComposition", "Source\ViewModelComposition\ViewModelComposition.csproj", "{7AA1C956-8486-4C9C-8226-4716D7E8212D}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandPattern", "Source\CommandPattern\CommandPattern.csproj", "{F7EAF027-33A4-4FB5-803E-40AEE57F309D}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4318BA69-FFF2-4DBD-AD08-01D5AD4E6E39}" 15 | ProjectSection(SolutionItems) = preProject 16 | .editorconfig = .editorconfig 17 | .gitattributes = .gitattributes 18 | .gitignore = .gitignore 19 | EndProjectSection 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{68AB6848-44A9-4AAD-A415-13F223BC85AA}" 22 | ProjectSection(SolutionItems) = preProject 23 | LICENSE = LICENSE 24 | README.md = README.md 25 | EndProjectSection 26 | EndProject 27 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventAggregatorPattern", "Source\EventAggregatorPattern\EventAggregatorPattern.csproj", "{3D183B21-0297-45EC-B6D7-DB4909064DA9}" 28 | EndProject 29 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rx", "Source\Rx\Rx.csproj", "{9FEEEE62-758D-430D-9F49-CF947BB33FEE}" 30 | EndProject 31 | Global 32 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 33 | Debug|Any CPU = Debug|Any CPU 34 | Release|Any CPU = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 37 | {55613994-149C-4D48-A06E-069EEB0935D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {55613994-149C-4D48-A06E-069EEB0935D0}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {55613994-149C-4D48-A06E-069EEB0935D0}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {55613994-149C-4D48-A06E-069EEB0935D0}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {7AA1C956-8486-4C9C-8226-4716D7E8212D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {7AA1C956-8486-4C9C-8226-4716D7E8212D}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {7AA1C956-8486-4C9C-8226-4716D7E8212D}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {7AA1C956-8486-4C9C-8226-4716D7E8212D}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {F7EAF027-33A4-4FB5-803E-40AEE57F309D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {F7EAF027-33A4-4FB5-803E-40AEE57F309D}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {F7EAF027-33A4-4FB5-803E-40AEE57F309D}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {F7EAF027-33A4-4FB5-803E-40AEE57F309D}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {3D183B21-0297-45EC-B6D7-DB4909064DA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {3D183B21-0297-45EC-B6D7-DB4909064DA9}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {3D183B21-0297-45EC-B6D7-DB4909064DA9}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {3D183B21-0297-45EC-B6D7-DB4909064DA9}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {9FEEEE62-758D-430D-9F49-CF947BB33FEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {9FEEEE62-758D-430D-9F49-CF947BB33FEE}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {9FEEEE62-758D-430D-9F49-CF947BB33FEE}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {9FEEEE62-758D-430D-9F49-CF947BB33FEE}.Release|Any CPU.Build.0 = Release|Any CPU 57 | EndGlobalSection 58 | GlobalSection(SolutionProperties) = preSolution 59 | HideSolutionNode = FALSE 60 | EndGlobalSection 61 | GlobalSection(NestedProjects) = preSolution 62 | {55613994-149C-4D48-A06E-069EEB0935D0} = {E33EDBE9-3502-465B-A4CB-C3D4D471FCE2} 63 | {7AA1C956-8486-4C9C-8226-4716D7E8212D} = {E33EDBE9-3502-465B-A4CB-C3D4D471FCE2} 64 | {F7EAF027-33A4-4FB5-803E-40AEE57F309D} = {E33EDBE9-3502-465B-A4CB-C3D4D471FCE2} 65 | {3D183B21-0297-45EC-B6D7-DB4909064DA9} = {E33EDBE9-3502-465B-A4CB-C3D4D471FCE2} 66 | {9FEEEE62-758D-430D-9F49-CF947BB33FEE} = {E33EDBE9-3502-465B-A4CB-C3D4D471FCE2} 67 | EndGlobalSection 68 | GlobalSection(ExtensibilityGlobals) = postSolution 69 | SolutionGuid = {381734F3-5429-4DF7-BCB8-8F68E6E7D5FC} 70 | EndGlobalSection 71 | EndGlobal 72 | -------------------------------------------------------------------------------- /Source/Rx/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Rx 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Reactive.Linq; 6 | using System.Reactive.Subjects; 7 | using Common; 8 | using DryIoc; 9 | 10 | public class Program 11 | { 12 | public static void Main() 13 | { 14 | var container = new Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient()); 15 | container.Register(Reuse.Singleton); 16 | container.Register(Reuse.Singleton); 17 | container.Register(Reuse.Singleton); 18 | container.Register(Reuse.Singleton); 19 | 20 | CSharpEventUsage.Execute(container); 21 | ReactiveExtensionsUsage.Execute(container); 22 | 23 | Console.Read(); 24 | } 25 | } 26 | 27 | #region C# Events 28 | 29 | public static class CSharpEventUsage 30 | { 31 | public static void Execute(Container container) 32 | { 33 | var subscriberViewModel = container.Resolve(); 34 | var publisherViewModel = container.Resolve(); 35 | publisherViewModel.RaiseNewMessage("Hello"); 36 | } 37 | } 38 | 39 | public class NewMessageEventArgs : EventArgs 40 | { 41 | public NewMessageEventArgs(string text) => this.Text = text; 42 | 43 | public string Text { get; } 44 | } 45 | 46 | public class CSharpEventPublisherViewModel 47 | { 48 | public event EventHandler NewMessage; 49 | 50 | public void RaiseNewMessage(string text) => this.NewMessage?.Invoke(this, new NewMessageEventArgs(text)); 51 | } 52 | 53 | public class CSharpEventSubscriberViewModel : Disposable 54 | { 55 | private readonly CSharpEventPublisherViewModel publisherViewModel; 56 | 57 | public CSharpEventSubscriberViewModel(CSharpEventPublisherViewModel publisherViewModel) 58 | { 59 | this.publisherViewModel = publisherViewModel; 60 | publisherViewModel.NewMessage += this.OnNewMessage; 61 | } 62 | 63 | protected override void DisposeManaged() => this.publisherViewModel.NewMessage -= this.OnNewMessage; 64 | 65 | private void OnNewMessage(object sender, NewMessageEventArgs e) => Console.WriteLine(e.Text); 66 | } 67 | 68 | #endregion 69 | 70 | #region IEnumerator is Dual of IObserver 71 | 72 | public class Dual 73 | { 74 | public Dual() 75 | { 76 | #pragma warning disable CS0219 // Shhh...sleep C# compiler 77 | IEnumerable enumerable = null; 78 | IEnumerator enumerator = null; 79 | 80 | IObservable observable = null; 81 | IObserver observer = null; 82 | #pragma warning restore CS0219 83 | } 84 | } 85 | 86 | #endregion 87 | 88 | #region Reactive Extensions 89 | 90 | public static class ReactiveExtensionsUsage 91 | { 92 | public static void Execute(Container container) 93 | { 94 | var subscriberViewModel = container.Resolve(); 95 | var publisherViewModel = container.Resolve(); 96 | 97 | publisherViewModel.NewMessage("Hi"); 98 | publisherViewModel.NewMessage("Hello"); 99 | } 100 | } 101 | 102 | public class RxPublisherViewModel 103 | { 104 | private readonly Subject newMessageSubject = new Subject(); 105 | 106 | public IObservable WhenNewMessage => this.newMessageSubject.AsObservable(); 107 | 108 | public void NewMessage(string text) => this.newMessageSubject.OnNext(text); 109 | } 110 | 111 | public class RxSubscriberViewModel : Disposable 112 | { 113 | private readonly IDisposable newMessageSubscription; 114 | 115 | public RxSubscriberViewModel(RxPublisherViewModel publisherViewModel) => 116 | this.newMessageSubscription = publisherViewModel.WhenNewMessage 117 | .Where(x => x.Contains("Hello")) 118 | .Select(x => $"{x} ({x.Length})") 119 | .Throttle(TimeSpan.FromSeconds(1)) 120 | // .ObserveOnDispatcher(); 121 | .Subscribe(this.OnNewMessage); 122 | 123 | protected override void DisposeManaged() => this.newMessageSubscription.Dispose(); 124 | 125 | private void OnNewMessage(string text) => Console.WriteLine(text); 126 | } 127 | 128 | #endregion 129 | 130 | 131 | } 132 | -------------------------------------------------------------------------------- /Source/Common/RevertibleChangeTracking.cs: -------------------------------------------------------------------------------- 1 | namespace Common 2 | { 3 | using System; 4 | using System.ComponentModel; 5 | using System.Runtime.CompilerServices; 6 | 7 | /// 8 | /// Provides support for rolling back changes made to this objects properties. This object 9 | /// automatically saves its state before it is changed. Also provides errors for the object if 10 | /// it is in an invalid state. 11 | /// 12 | /// The type of this instance. 13 | public abstract class RevertibleChangeTracking : EditableObject, IRevertibleChangeTracking, IEquatable 14 | where T : RevertibleChangeTracking, new() 15 | { 16 | private bool isChanged; 17 | private bool isChangeTrackingEnabled; 18 | private bool isNew = true; 19 | 20 | /// 21 | /// Gets or sets a value indicating whether the change tracking of the object's status is enabled. 22 | /// 23 | /// 24 | /// true if change tracking ir enabled; otherwise, false. 25 | /// 26 | public bool IsChangeTrackingEnabled 27 | { 28 | get => this.isChangeTrackingEnabled; 29 | set 30 | { 31 | base.OnPropertyChanging("IsChangeTrackingEnabled"); 32 | this.isChangeTrackingEnabled = value; 33 | base.OnPropertyChanged("IsChangeTrackingEnabled"); 34 | } 35 | } 36 | 37 | /// 38 | /// Gets a value indicating whether the object's status changed. 39 | /// 40 | /// 41 | /// true if the object’s content has changed since the last call to ; otherwise, false. 42 | /// 43 | public bool IsChanged 44 | { 45 | get => this.isChanged; 46 | 47 | private set 48 | { 49 | base.OnPropertyChanging("IsChanged"); 50 | this.isChanged = value; 51 | base.OnPropertyChanged("IsChanged"); 52 | } 53 | } 54 | 55 | /// 56 | /// Gets a value indicating whether this instance is new. 57 | /// 58 | /// true if this instance is new; otherwise, false. 59 | public bool IsNew 60 | { 61 | get => this.isNew; 62 | private set => this.SetProperty(ref this.isNew, value); 63 | } 64 | 65 | /// 66 | /// Resets the object’s state to unchanged by accepting the modifications. 67 | /// 68 | public virtual void AcceptChanges() 69 | { 70 | if (this.IsNew) 71 | { 72 | this.IsNew = false; 73 | this.IsChangeTrackingEnabled = true; 74 | } 75 | else if (this.IsChanged) 76 | { 77 | this.EndEdit(); 78 | 79 | this.IsChanged = false; 80 | } 81 | } 82 | 83 | /// 84 | /// Discards changes since the last call. 85 | /// 86 | public override void CancelEdit() 87 | { 88 | base.CancelEdit(); 89 | 90 | this.IsChanged = false; 91 | } 92 | 93 | /// 94 | /// Indicates whether the current object is equal to another object of the same type. 95 | /// 96 | /// An object to compare with this object. 97 | /// 98 | /// true if the current object is equal to the parameter; otherwise, false. 99 | /// 100 | public virtual bool Equals(T other) => object.Equals(this, other); 101 | 102 | /// 103 | /// Resets the object’s state to unchanged by rejecting the modifications. 104 | /// 105 | public virtual void RejectChanges() => this.CancelEdit(); 106 | 107 | /// 108 | /// Raises the PropertyChanged event. 109 | /// 110 | /// Name of the property. 111 | protected override void OnPropertyChanged([CallerMemberName] string propertyName = null) 112 | { 113 | base.OnPropertyChanged(propertyName); 114 | 115 | if (this.IsChangeTrackingEnabled) 116 | { 117 | if (this.Equals(this.Original)) 118 | { 119 | this.IsChanged = false; 120 | } 121 | else 122 | { 123 | this.IsChanged = true; 124 | } 125 | } 126 | } 127 | 128 | /// 129 | /// Raises the PropertyChanging event. 130 | /// 131 | /// Name of the property. 132 | protected override void OnPropertyChanging([CallerMemberName] string propertyName = null) 133 | { 134 | if (this.IsChangeTrackingEnabled) 135 | { 136 | this.BeginEdit(); 137 | } 138 | 139 | base.OnPropertyChanging(propertyName); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Source/Common/EditableObject.cs: -------------------------------------------------------------------------------- 1 | namespace Common 2 | { 3 | using System; 4 | using System.ComponentModel; 5 | using System.Reactive; 6 | using System.Reactive.Linq; 7 | using System.Reactive.Subjects; 8 | 9 | /// 10 | /// Provides functionality to commit or rollback changes to an object that is used as a data 11 | /// source and provide errors for the object if it is in an invalid state. 12 | /// 13 | /// The type of this instance. 14 | public abstract class EditableObject : NotifyDataErrorInfo, ICloneable, IEditableObject 15 | where T : EditableObject, new() 16 | { 17 | private readonly Subject beginEditingSubject = new Subject(); 18 | private readonly Subject cancelEditingSubject = new Subject(); 19 | private readonly Subject endEditingSubject = new Subject(); 20 | 21 | /// 22 | /// Gets the when begin editing observable event. Occurs when beginning editing. 23 | /// 24 | /// 25 | /// The when begin editing observable event. 26 | /// 27 | public IObservable WhenBeginEditing => this.beginEditingSubject.AsObservable(); 28 | 29 | /// 30 | /// Gets the when cancel editing observable event. Occurs when cancelling editing. 31 | /// 32 | /// 33 | /// The when begin cancel observable event. 34 | /// 35 | public IObservable WhenCancelEditing => this.cancelEditingSubject.AsObservable(); 36 | 37 | /// 38 | /// Gets the when end editing observable event. Occurs when ending editing. 39 | /// 40 | /// 41 | /// The when begin end observable event. 42 | /// 43 | public IObservable WhenEndEditing => this.endEditingSubject.AsObservable(); 44 | 45 | /// 46 | /// Gets the original version of this object before was called. 47 | /// 48 | /// The original version of this object before was called. 49 | public T Original { get; private set; } 50 | 51 | /// 52 | /// Clones the clonable object of type . 53 | /// 54 | /// 55 | /// The cloned object of type . 56 | /// 57 | public T Clone() 58 | { 59 | var clone = new T(); 60 | clone.Load((T)this); 61 | return clone; 62 | } 63 | 64 | /// 65 | /// Creates a new object that is a copy of the current instance. 66 | /// 67 | /// 68 | /// A new object that is a copy of this instance. 69 | /// 70 | object ICloneable.Clone() => this.Clone(); 71 | 72 | /// 73 | /// Begins an edit on an object. 74 | /// 75 | public virtual void BeginEdit() 76 | { 77 | if (this.Original == null) 78 | { 79 | this.Original = this.Clone(); 80 | 81 | this.OnBeginEditing(); 82 | } 83 | } 84 | 85 | /// 86 | /// Discards changes since the last call. 87 | /// 88 | public virtual void CancelEdit() 89 | { 90 | if (this.Original != null) 91 | { 92 | this.Load(this.Original); 93 | 94 | this.Original = null; 95 | 96 | this.OnCancelEditing(); 97 | } 98 | } 99 | 100 | /// 101 | /// Pushes changes since the last or call into the underlying object. 102 | /// 103 | public virtual void EndEdit() 104 | { 105 | this.Original = null; 106 | 107 | this.OnEndEditing(); 108 | } 109 | 110 | /// 111 | /// Loads the specified item. 112 | /// 113 | /// The item to load this instance from. 114 | public abstract void Load(T item); 115 | 116 | /// 117 | /// Disposes the managed resources implementing . 118 | /// 119 | protected override void DisposeManaged() 120 | { 121 | this.beginEditingSubject.Dispose(); 122 | this.cancelEditingSubject.Dispose(); 123 | this.endEditingSubject.Dispose(); 124 | } 125 | 126 | /// 127 | /// Called when editing has began. 128 | /// 129 | protected virtual void OnBeginEditing() => this.beginEditingSubject.OnNext(Unit.Default); 130 | 131 | /// 132 | /// Called when editing is cancelled. 133 | /// 134 | protected virtual void OnCancelEditing() => this.cancelEditingSubject.OnNext(Unit.Default); 135 | 136 | /// 137 | /// Called when editing has ended. 138 | /// 139 | protected virtual void OnEndEditing() => this.endEditingSubject.OnNext(Unit.Default); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Source/CommandPattern/Program.cs: -------------------------------------------------------------------------------- 1 | namespace CommandPattern 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | using System.Windows.Input; 6 | using Common; 7 | using Common.Commands; 8 | using Common.Services; 9 | using DryIoc; 10 | 11 | /// 12 | /// See . 13 | /// 14 | /// 15 | /// Command="{Binding Foo}" 16 | /// CommandParameter="{Binding Bar}" 17 | /// 18 | public class Program 19 | { 20 | public static async Task Main() 21 | { 22 | var container = new Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient()); 23 | container.Register(); 24 | container.Register(); 25 | container.Register(); 26 | 27 | container.Register(); 28 | await FirstUsage.Execute(container); 29 | 30 | container.Register(); 31 | container.Register(); 32 | SecondUsage.Execute(container); 33 | 34 | container.Register(); 35 | container.Register(); 36 | ThirdUsage.Execute(container); 37 | 38 | Console.Read(); 39 | } 40 | } 41 | 42 | #region First 43 | 44 | public static class FirstUsage 45 | { 46 | public static async Task Execute(Container container) 47 | { 48 | var firstViewModel = container.Resolve(); 49 | 50 | if (firstViewModel.NoParameterCommand.CanExecute()) 51 | { 52 | firstViewModel.NoParameterCommand.Execute(); 53 | } 54 | 55 | if (firstViewModel.HasParameterCommand.CanExecute("Hi")) 56 | { 57 | firstViewModel.HasParameterCommand.Execute("Hi"); 58 | } 59 | 60 | if (firstViewModel.NoParameterAsyncCommand.CanExecute()) 61 | { 62 | await firstViewModel.NoParameterAsyncCommand.Execute(); 63 | } 64 | 65 | if (firstViewModel.HasParameterAsyncCommand.CanExecute("Hi")) 66 | { 67 | await firstViewModel.HasParameterAsyncCommand.Execute("Hi"); 68 | } 69 | } 70 | } 71 | 72 | public class FirstViewModel : NotifyPropertyChanges 73 | { 74 | public FirstViewModel() 75 | { 76 | this.NoParameterCommand = new DelegateCommand(this.NoParameter); 77 | this.HasParameterCommand = new DelegateCommand(this.HasParameter); 78 | this.NoParameterAsyncCommand = new AsyncDelegateCommand(this.NoParameterAsync); 79 | this.HasParameterAsyncCommand = new AsyncDelegateCommand(this.HasParameterAsync); 80 | } 81 | 82 | public DelegateCommand NoParameterCommand { get; } 83 | 84 | public DelegateCommand HasParameterCommand { get; } 85 | 86 | public AsyncDelegateCommand NoParameterAsyncCommand { get; } 87 | 88 | public AsyncDelegateCommand HasParameterAsyncCommand { get; } 89 | 90 | private void NoParameter() => Console.WriteLine("Hello World"); 91 | 92 | private void HasParameter(string greeting) => Console.WriteLine($"{greeting}"); 93 | 94 | private Task NoParameterAsync() 95 | { 96 | Console.WriteLine("Async Hello World"); 97 | return Task.CompletedTask; 98 | } 99 | 100 | private Task HasParameterAsync(string greeting) 101 | { 102 | Console.WriteLine($"Async {greeting}"); 103 | return Task.CompletedTask; 104 | } 105 | } 106 | 107 | #endregion 108 | 109 | #region Second 110 | 111 | public static class SecondUsage 112 | { 113 | public static void Execute(Container container) 114 | { 115 | var secondViewModel = container.Resolve(); 116 | if (secondViewModel.GreetingCommand.CanExecute("Foo")) 117 | { 118 | // This should never execute 119 | } 120 | 121 | if (secondViewModel.GreetingCommand.CanExecute("Hello")) 122 | { 123 | secondViewModel.GreetingCommand.Execute("Hello"); 124 | } 125 | } 126 | } 127 | 128 | public class SecondViewModel : NotifyPropertyChanges 129 | { 130 | private readonly IService2 service2; 131 | 132 | public SecondViewModel( 133 | GreetingCommand greetingCommand, 134 | IService2 service2) 135 | { 136 | this.GreetingCommand = greetingCommand; 137 | this.service2 = service2; 138 | } 139 | 140 | // returning false from CanExecute can cause a control's IsEnabled property to be set to false. 141 | // You can use this behaviour to do other things like hide the control: 142 | // 153 | public GreetingCommand GreetingCommand { get; } 154 | } 155 | 156 | public class GreetingCommand : Command 157 | { 158 | private readonly IService1 service1; 159 | 160 | public GreetingCommand(IService1 service1) => this.service1 = service1; 161 | 162 | public override bool CanExecute(string parameter) => parameter.Contains("H"); 163 | 164 | public override void Execute(string greeting) => Console.WriteLine(greeting); 165 | } 166 | 167 | #endregion 168 | 169 | #region Third 170 | 171 | public static class ThirdUsage 172 | { 173 | public static void Execute(Container container) 174 | { 175 | var thirdViewModel = container.Resolve(); 176 | if (thirdViewModel.DoStuffOnTheViewModelCommand.CanExecute(thirdViewModel)) 177 | { 178 | thirdViewModel.DoStuffOnTheViewModelCommand.Execute(thirdViewModel); 179 | } 180 | } 181 | } 182 | 183 | public class ThirdViewModel : NotifyPropertyChanges 184 | { 185 | private readonly IService3 service3; 186 | private string text; 187 | 188 | public ThirdViewModel( 189 | DoStuffOnTheViewModelCommand doStuffOnTheViewModelCommand, 190 | IService3 service3) 191 | { 192 | this.DoStuffOnTheViewModelCommand = doStuffOnTheViewModelCommand; 193 | this.service3 = service3; 194 | } 195 | 196 | //