Exemplo n.º 1
0
        /// <summary>
        /// Binds the property on many elements into a combination for which <paramref name="handler"/> is invoked whenever its value changes.
        /// This is useful for instance when you have a property on a collection that is the reduction of a property of collection element (in which case you can use the handler to set the reduced value).
        /// </summary>
        /// <typeparam name="TElement"> The type of the elements of the specified observable collection. </typeparam>
        /// <typeparam name="TElementProperty"> The type of the property on <typeparamref name="TElement"/> whose name is <paramref name="propertyName"/> and whose values are to be reduced. </typeparam>
        /// <param name="collection"> The collection to monitor for property changes. </param>
        /// <param name="propertyName"> The name of the property on the collection elements to monitor. </param>
        /// <param name="handler"> The function handling the change of the reduced value. The first argument is the collection and the second the new value. </param>
        /// <param name="combine"> The function taking the previous reduced value and an updated value of one of the properies, and returns their reduction. </param>
        /// <param name="valueIfCollectionEmpty"> The value to be taken if the specified collection is empty. </param>
        /// <param name="equalityComparer"> The function comparing <typeparamref name="TElementProperty"/>s for equality, determining when to invoke <paramref name="handler"/>. </param>
        public static void BindCollective <TElement, TElementProperty>(this INotifyCollectionChanged collection,
                                                                       string propertyName,
                                                                       PropertyChangedEventHandler handler,
                                                                       Func <TElementProperty /*previous resultant*/, TElementProperty /*new property*/, TElementProperty> combine,
                                                                       Option <TElementProperty> valueIfCollectionEmpty      = default,
                                                                       IEqualityComparer <TElementProperty> equalityComparer = null)
        {
            Contract.Requires(collection != null);
            Contract.Requires(collection is IEnumerable <TElement>);
            Contract.Requires(HasProperty <TElement>(propertyName));
            Contract.Requires(handler != null);
            Contract.Requires(combine != null);

            equalityComparer = equalityComparer ?? EqualityComparer <TElementProperty> .Default;

            Option <TElementProperty> resultant = Option <TElementProperty> .None;

            collection.Bind(propertyName, elementPropertyChanged, typeof(TElement), true, onElementRemoved);
            setResultant(recomputeResultant());             // if handler shouldn't be triggered on the initially present elements, set as resultant = recomputeResultant() rather than through setResultant


            Option <TElementProperty> recomputeResultant()
            {
                Option <TElementProperty> result = Option <TElementProperty> .None;

                foreach (var element in (IEnumerable <TElement>)collection)
                {
                    var propValue = GetPropertyValue <TElement, TElementProperty>(element, propertyName);

                    if (!result.HasValue)
                    {
                        result = propValue;
                    }
                    else
                    {
                        result = combine(result.Value, propValue);
                    }
                }
                return(result);                // returns Option.None when the collection is empty
            }

            void onElementRemoved(object sender, PropertyChangedEventArgs e)
            {
                Contract.Assert(resultant.HasValue);

                var propValue = GetPropertyValue <TElement, TElementProperty>((TElement)sender, e.PropertyName);

                if (equalityComparer.Equals(propValue, resultant.Value))                 // if the resultant value was removed from the collection
                {
                    setResultant(recomputeResultant());
                }
            }

            void elementPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (e.PropertyName == propertyName)
                {
                    var newValue     = GetPropertyValue <TElement, TElementProperty>((TElement)sender, e.PropertyName);
                    var newResultant = resultant.HasValue ? combine(resultant.Value, newValue) : newValue;
                    setResultant(newResultant);
                }
            }

            void setResultant(Option <TElementProperty> newResultant)
            {
                // newResultant: None means the collection is empty.
                TElementProperty newValue;

                if (newResultant.HasValue)
                {
                    newValue = newResultant.Value;
                }
                else
                {
                    if (valueIfCollectionEmpty.HasValue)
                    {
                        newValue = valueIfCollectionEmpty.Value;
                    }
                    else
                    {
                        // don't trigger the handler if there is no value to set
                        return;
                    }
                }

                if (resultant.HasValue)
                {
                    TElementProperty oldValue = resultant.Value;
                    if (!equalityComparer.Equals(oldValue, newValue))
                    {
                        handle(oldValue, newValue);
                    }
                }
                else
                {
                    TElementProperty oldValue = valueIfCollectionEmpty.ValueOrDefault();
                    handle(oldValue, newValue);
                }
            }

            void handle(TElementProperty oldValue, TElementProperty newValue)
            {
                var arg = new PropertyMutatedEventArgs <TElementProperty>(propertyName, oldValue, newValue);

                handler(collection, arg);
            }
        }