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));
}
}