using System; using System.Collections.Generic; using System.ComponentModel; namespace CSharpVitamins.PropertyChangeFilter { public class PropertyChangeFilter : INotifyPropertyChanged { #region Private Fields bool _hasChanged; bool _trackChanges = false; StringComparison _comparisonType = StringComparison.Ordinal; string[] _properties; INotifyPropertyChanged _target; List _changes; #endregion #region Constructors /// /// /// public PropertyChangeFilter() { } /// /// /// /// The object to listen for property changes on. public PropertyChangeFilter(INotifyPropertyChanged target) : this(target, null) { } /// /// /// /// The list of property names to filter changes for public PropertyChangeFilter(params string[] names) : this(null, names) { } /// /// /// /// The object to listen for property changes on. /// The list of property names to filter changes for public PropertyChangeFilter(INotifyPropertyChanged target, params string[] names) { Properties = names; Target = target; } #endregion #region Properties /// /// Gets whether the PropertyChanged event has passed through /// the filters. This is marked as changed ofter the first sucessful /// pass through. /// public bool HasChanged { get { return _hasChanged; } } /// /// Gets/Sets whether a list of the property changes is kept. /// /// Only the property names are stored. public bool TrackChanges { get { return _trackChanges; } set { _trackChanges = value; } } /// /// Gets a list of the property names that have passed the change filter /// /// TrackChanges must be true for the change list to be populated. public List Changes { get { return _changes ?? (_changes = new List()); } } /// /// Gets/Sets the comparison type to use when listening for /// property names i.e. case sensitive / insensitive /// public StringComparison ComparisonType { get { return _comparisonType; } set { _comparisonType = value; } } /// /// Gets/Sets the list of property names to filter changes for /// /// /// If Properties is null or zero length, all properties will /// sucessfully pass through the filter. /// /// Use the ComparisonType property to configure filtering for /// case-insensitive names. /// public string[] Properties { get { return _properties; } set { _properties = value; } } /// /// Gets/Sets the object to listen for property changes on /// public INotifyPropertyChanged Target { get { return _target; } set { if (_target != value) { // remove old listener if (null != _target) { _target.PropertyChanged -= filter; } Reset(); _target = value; // add new listener if (null != _target) { _target.PropertyChanged += filter; } } } } #endregion #region Methods /// /// Resets the IsChanged and and tracked changes to default values. /// public void Reset() { _hasChanged = false; _changes = null; } /// /// /// /// /// void filter(object sender, PropertyChangedEventArgs e) { if (null != _properties && _properties.Length > 0) { /// Create a predicate that has a closure to our property name /// in the EventArgs for searching our property names array Predicate match = delegate(string name) { return name.Equals(e.PropertyName, _comparisonType); }; if (!Array.Exists(_properties, match)) { /// don't pass event on, just return return; } } /// at this point our filters have been passed and the /// event can be passed through to our other subscribers. trackChange(e.PropertyName); /// NB: The original object is passed through, not the actual /// filter instance OnPropertyChanged(sender, e); } /// /// Sets the IsChanged flag to true and if enabled, adds an entry for the /// property name to the changes collections. /// /// void trackChange(string name) { _hasChanged = true; if (_trackChanges) { Changes.Add(name); } } #endregion #region Events /// /// Occurs when a property is changed /// public event PropertyChangedEventHandler PropertyChanged; /// /// Raises the PropertyChanged event /// /// protected void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { PropertyChangedEventHandler handler = PropertyChanged; if (null != handler) { handler(sender, e); } } #endregion } }