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 bool _useDeferredNotifications; /// /// Initializes a new instance of the class. /// /// Set to true to use deferred notifications in the background. protected ViewModelBase(bool useDeferredNotifications) { _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 bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = "", string[] notifyAlso = null) { if (EqualityComparer.Default.Equals(storage, value)) return false; storage = value; NotifyPropertyChanged(propertyName, notifyAlso); return true; } /// /// Notifies one or more properties changed. /// /// The property names. protected void NotifyPropertyChanged(params string[] propertyNames) => 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) _queuedNotifications[mainProperty] = true; // Set the state for notification properties if (auxiliaryProperties != null) { foreach (var property in auxiliaryProperties) { if (string.IsNullOrWhiteSpace(property) == false) _queuedNotifications[property] = true; } } // Depending on operation mode, either fire the notifications in the background // or fire them immediately if (_useDeferredNotifications) Task.Run(NotifyQueuedProperties); else NotifyQueuedProperties(); } /// /// Notifies the queued properties and resets the property name to a non-queued stated. /// private void NotifyQueuedProperties() { // get a snapshot of property names. var propertyNames = _queuedNotifications.Keys.ToArray(); // Iterate through the properties foreach (var property in propertyNames) { // don't notify if we don't have a change if (!_queuedNotifications[property]) continue; // notify and reset queued state to false try { OnPropertyChanged(property); } finally { _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)); } }