#region Apache License 2.0 /* Nuclex .NET Framework Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #endregion // Apache License 2.0 using System; #if NO_CONCURRENT_COLLECTIONS using System.Collections.Generic; #else using System.Collections.Concurrent; #endif using System.ComponentModel; #if !NO_LINQ_EXPRESSIONS using System.Linq.Expressions; #endif namespace Nuclex.Support { /// Contains helper methods for property change notifications public static class PropertyChangedEventArgsHelper { /// /// A property change event argument container that indicates that all /// properties have changed their value. /// public static readonly PropertyChangedEventArgs Wildcard = new PropertyChangedEventArgs(null); /// Initializes a new property changed argument helper static PropertyChangedEventArgsHelper() { #if NO_CONCURRENT_COLLECTIONS cache = new Dictionary(); #else cache = new ConcurrentDictionary(); #endif } #if !NO_LINQ_EXPRESSIONS /// /// Provides a property change argument container for the specified property /// /// /// Property for which an event argument container will be provided /// /// The event argument container for a property of the specified name /// /// /// This method transparently caches instances of the argument containers /// to avoid feeding the garbage collector. A typical application only has /// in the order of tens to hundreds of different properties for which changes /// will be reported, making a cache to avoid garbage collections viable. /// /// /// /// PropertyChangedEventArgs arguments = /// PropertyChangedEventArgsHelper.GetArgumentsFor(() => SomeProperty); /// /// /// [Obsolete("Prefer the C# 'nameof()' operator to using a Linq expression")] public static PropertyChangedEventArgs GetArgumentsFor( Expression> property ) { return GetArgumentsFor(ObservableHelper.GetPropertyName(property)); } #endif /// /// Provides a property change argument container for the specified property /// /// /// Property for which an event argument container will be provided /// /// The event argument container for a property of the specified name /// /// /// This method transparently caches instances of the argument containers /// to avoid feeding the garbage collector. A typical application only has /// in the order of tens to hundreds of different properties for which changes /// will be reported, making a cache to avoid garbage collections viable. /// /// /// /// PropertyChangedEventArgs arguments = /// PropertyChangedEventArgsHelper.GetArgumentsFor("SomeProperty"); /// /// /// public static PropertyChangedEventArgs GetArgumentsFor(string propertyName) { if(string.IsNullOrEmpty(propertyName)) { return Wildcard; } #if NO_CONCURRENT_COLLECTIONS lock(cache) { // Try to reuse the change notification if an instance already exists PropertyChangedEventArgs arguments; if(!cache.TryGetValue(propertyName, out arguments)) { arguments = new PropertyChangedEventArgs(propertyName); cache.Add(propertyName, arguments); } return arguments; } #else // If an instance for this property already exists, just return it PropertyChangedEventArgs arguments; if(cache.TryGetValue(propertyName, out arguments)) { return arguments; } // No instance existed (at least a short moment ago), so create a new one return cache.GetOrAdd(propertyName, new PropertyChangedEventArgs(propertyName)); #endif } #if !NO_LINQ_EXPRESSIONS /// /// Determines whether the property change affects the specified property /// /// /// Type of the property that will be tested for being affected /// /// /// Property change that has been reported by the observed object /// /// Property that will be tested for being affected /// Whether the specified property is affected by the property change /// /// /// By using this method, you can shorten the code needed to test whether /// a property change notification affects a specific property. You also /// avoid hardcoding the property name, which would have the adverse effect /// of not updating the textual property names during F2 refactoring. /// /// /// /// private void propertyChanged(object sender, PropertyChangedEventArgs arguments) { /// if(arguments.AreAffecting(() => ViewModel.DisplayedValue)) { /// updateDisplayedValueFromViewModel(); /// } // Do not use else if here or wildcards will not work /// if(arguments.AreAffecting(() => ViewModel.OtherValue)) { /// updateOtherValueFromViewModel(); /// } /// } /// /// /// [Obsolete("Prefer the C# 'nameof()' operator to using a Linq expression")] public static bool AreAffecting( this PropertyChangedEventArgs arguments, Expression> property ) { if(arguments.AffectAllProperties()) { return true; } string propertyName = ObservableHelper.GetPropertyName(property); return (arguments.PropertyName == propertyName); } #endif /// /// Determines whether the property change affects the specified property /// /// /// Property change that has been reported by the observed object /// /// Property that will be tested for being affected /// Whether the specified property is affected by the property change /// /// /// By using this method, you can shorten the code needed to test whether /// a property change notification affects a specific property. /// /// /// /// private void propertyChanged(object sender, PropertyChangedEventArgs arguments) { /// if(arguments.AreAffecting("DisplayedValue")) { /// updateDisplayedValueFromViewModel(); /// } // Do not use else if here or wildcards will not work /// if(arguments.AreAffecting("OtherValue")) { /// updateOtherValueFromViewModel(); /// } /// } /// /// /// public static bool AreAffecting( this PropertyChangedEventArgs arguments, string propertyName ) { if(arguments.AffectAllProperties()) { return true; } return (arguments.PropertyName == propertyName); } /// Determines whether a property change notification is a wildcard /// /// Property change notification that will be checked on being a wildcard /// /// /// Whether the property change is a wildcard, indicating that all properties /// have changed. /// /// /// /// As stated on MSDN: "The PropertyChanged event can indicate all properties /// on the object have changed by using either Nothing or String.Empty as /// the property name in the PropertyChangedEventArgs." /// /// /// This method offers an expressive way of checking for that eventuality. /// /// /// /// private void propertyChanged(object sender, PropertyChangedEventArgs arguments) { /// if(arguments.AffectAllProperties()) { /// // Do something /// } /// } /// /// /// public static bool AffectAllProperties(this PropertyChangedEventArgs arguments) { return string.IsNullOrEmpty(arguments.PropertyName); } /// /// Caches PropertyChangedEventArgs instances to avoid feeding the garbage collector /// #if NO_CONCURRENT_COLLECTIONS private static readonly Dictionary cache; #else private static readonly ConcurrentDictionary cache; #endif } } // namespace Nuclex.Support