using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace Swan { /// /// A base class for implementing models that fire notifications when their properties change. /// This class is ideal for implementing MVVM driven UIs. /// /// public abstract class ViewModelBase : INotifyPropertyChanged { private readonly ConcurrentDictionary _queuedNotifications = new ConcurrentDictionary(); private readonly Boolean _useDeferredNotifications; /// /// Initializes a new instance of the class. /// /// Set to true to use deferred notifications in the background. protected ViewModelBase(Boolean useDeferredNotifications) => this._useDeferredNotifications = useDeferredNotifications; /// /// Initializes a new instance of the class. /// protected ViewModelBase() : this(false) { // placeholder } /// public event PropertyChangedEventHandler PropertyChanged; /// Checks if a property already matches a desired value. Sets the property and /// notifies listeners only when necessary. /// Type of the property. /// Reference to a property with both getter and setter. /// Desired value for the property. /// Name of the property used to notify listeners. This /// value is optional and can be provided automatically when invoked from compilers that /// support CallerMemberName. /// An array of property names to notify in addition to notifying the changes on the current property name. /// True if the value was changed, false if the existing value matched the /// desired value. protected Boolean SetProperty(ref T storage, T value, [CallerMemberName] String propertyName = "", String[] notifyAlso = null) { if(EqualityComparer.Default.Equals(storage, value)) { return false; } storage = value; this.NotifyPropertyChanged(propertyName, notifyAlso); return true; } /// /// Notifies one or more properties changed. /// /// The property names. protected void NotifyPropertyChanged(params String[] propertyNames) => this.NotifyPropertyChanged(null, propertyNames); /// /// Notifies one or more properties changed. /// /// The main property. /// The auxiliary properties. private void NotifyPropertyChanged(String mainProperty, String[] auxiliaryProperties) { // Queue property notification if(String.IsNullOrWhiteSpace(mainProperty) == false) { this._queuedNotifications[mainProperty] = true; } // Set the state for notification properties if(auxiliaryProperties != null) { foreach(String property in auxiliaryProperties) { if(String.IsNullOrWhiteSpace(property) == false) { this._queuedNotifications[property] = true; } } } // Depending on operation mode, either fire the notifications in the background // or fire them immediately if(this._useDeferredNotifications) { _ = Task.Run(this.NotifyQueuedProperties); } else { this.NotifyQueuedProperties(); } } /// /// Notifies the queued properties and resets the property name to a non-queued stated. /// private void NotifyQueuedProperties() { // get a snapshot of property names. String[] propertyNames = this._queuedNotifications.Keys.ToArray(); // Iterate through the properties foreach(String property in propertyNames) { // don't notify if we don't have a change if(!this._queuedNotifications[property]) { continue; } // notify and reset queued state to false try { this.OnPropertyChanged(property); } finally { this._queuedNotifications[property] = false; } } } /// /// Called when a property changes its backing value. /// /// Name of the property. private void OnPropertyChanged(String propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName ?? String.Empty)); } }