File: System\Windows\Data\MultiBindingExpression.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
// Description: Defines MultiBindingExpression object, uses a collection of BindingExpressions together.
//
// See spec at Data Binding.mht
//
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Windows.Threading;
using System.Threading;
using System.Windows.Controls;
using System.Windows.Input;         // FocusChangedEvent
using System.Windows.Markup;
using MS.Internal.Controls; // Validation
using MS.Internal.KnownBoxes;
using MS.Internal.Data;
using MS.Utility;
using MS.Internal;                  // Invariant.Assert
 
namespace System.Windows.Data
{
/// <summary>
///  Describes a collection of BindingExpressions attached to a single property.
///     The inner BindingExpressions contribute their values to the MultiBindingExpression,
///     which combines/converts them into a resultant final value.
///     In the reverse direction, the target value is tranlated to
///     a set of values that are fed back into the inner BindingExpressions.
/// </summary>
public sealed class MultiBindingExpression: BindingExpressionBase, IDataBindEngineClient
{
    //------------------------------------------------------
    //
    //  Constructors
    //
    //------------------------------------------------------
 
    /// <summary> Constructor </summary>
    private MultiBindingExpression(MultiBinding binding, BindingExpressionBase owner)
        : base(binding, owner)
    {
        int count = binding.Bindings.Count;
 
        // reduce repeated allocations
        _tempValues = new object[count];
        _tempTypes = new Type[count];
    }
 
    //------------------------------------------------------
    //
    //  Interfaces
    //
    //------------------------------------------------------
 
    void IDataBindEngineClient.TransferValue()
    {
        TransferValue();
    }
 
    void IDataBindEngineClient.UpdateValue()
    {
        UpdateValue();
    }
 
    bool IDataBindEngineClient.AttachToContext(bool lastChance)
    {
        AttachToContext(lastChance);
        return !TransferIsDeferred;
    }
 
    void IDataBindEngineClient.VerifySourceReference(bool lastChance)
    {
    }
 
    void IDataBindEngineClient.OnTargetUpdated()
    {
        OnTargetUpdated();
    }
 
    DependencyObject IDataBindEngineClient.TargetElement
    {
        get { return !UsingMentor ? TargetElement : Helper.FindMentor(TargetElement); }
    }
 
    //------------------------------------------------------
    //
    //  Public Properties
    //
    //------------------------------------------------------
 
    /// <summary> Binding from which this expression was created </summary>
    public MultiBinding ParentMultiBinding { get { return (MultiBinding)ParentBindingBase; } }
 
    /// <summary> List of inner BindingExpression </summary>
    public ReadOnlyCollection<BindingExpressionBase>   BindingExpressions
    {
        get { return new ReadOnlyCollection<BindingExpressionBase>(MutableBindingExpressions); }
    }
 
    //------------------------------------------------------
    //
    //  Public Methods
    //
    //------------------------------------------------------
 
    /// <summary> Send the current value back to the source(s) </summary>
    /// <remarks> Does nothing when binding's Mode is not TwoWay or OneWayToSource </remarks>
    public override void UpdateSource()
    {
        // ultimately, what would be better would be to have a status flag that
        // indicates that this MultiBindingExpression has been Detached, as opposed to a
        // MultiBindingExpression that doesn't have anything in its BindingExpressions collection
        // in the first place.  Added to which, there should be distinct error
        // messages for both of these error conditions.
        if (MutableBindingExpressions.Count == 0)
            throw new InvalidOperationException(SR.BindingExpressionIsDetached);
 
        NeedsUpdate = true;     // force update
        Update();               // update synchronously
    }
 
    /// <summary> Force a data transfer from sources to target </summary>
    /// <remarks> Will transfer data even if binding's Mode is OneWay </remarks>
    public override void UpdateTarget()
    {
        // ultimately, what would be better would be to have a status flag that
        // indicates that this MultiBindingExpression has been Detached, as opposed to a
        // MultiBindingExpression that doesn't have anything in its BindingExpressions collection
        // in the first place.  Added to which, there should be distinct error
        // messages for both of these error conditions.
        if (MutableBindingExpressions.Count == 0)
            throw new InvalidOperationException(SR.BindingExpressionIsDetached);
 
        UpdateTarget(true);
    }
 
#region Expression overrides
 
#endregion  Expression overrides
 
    //------------------------------------------------------
    //
    //  Internal Properties
    //
    //------------------------------------------------------
 
    internal override bool IsParentBindingUpdateTriggerDefault
    {
        get { return (ParentMultiBinding.UpdateSourceTrigger == UpdateSourceTrigger.Default); }
    }
 
    //------------------------------------------------------
    //
    //  Internal Methods
    //
    //------------------------------------------------------
 
    // Create a new BindingExpression from the given Binding description
    internal static MultiBindingExpression CreateBindingExpression(DependencyObject d, DependencyProperty dp, MultiBinding binding, BindingExpressionBase owner)
    {
        FrameworkPropertyMetadata fwMetaData = dp.GetMetadata(d.DependencyObjectType) as FrameworkPropertyMetadata;
 
        if ((fwMetaData != null && !fwMetaData.IsDataBindingAllowed) || dp.ReadOnly)
            throw new ArgumentException(SR.Format(SR.PropertyNotBindable, dp.Name), "dp");
 
        // create the BindingExpression
        MultiBindingExpression bindExpr = new MultiBindingExpression(binding, owner);
 
        bindExpr.ResolvePropertyDefaultSettings(binding.Mode, binding.UpdateSourceTrigger, fwMetaData);
 
        return bindExpr;
    }
 
    // Attach to things that may require tree context (parent, root, etc.)
    void AttachToContext(bool lastChance)
    {
        DependencyObject target = TargetElement;
        if (target == null)
            return;
 
        Debug.Assert(ParentMultiBinding.Converter != null || !String.IsNullOrEmpty(EffectiveStringFormat),
                "MultiBindingExpression should not exist if its bind does not have a valid converter.");
 
        bool isExtendedTraceEnabled = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.AttachToContext);
 
        _converter = ParentMultiBinding.Converter;
        if (_converter == null && String.IsNullOrEmpty(EffectiveStringFormat) && TraceData.IsEnabled)
        {
            TraceData.TraceAndNotify(TraceEventType.Error, TraceData.MultiBindingHasNoConverter, this,
                traceParameters: new object[] { ParentMultiBinding });
        }
 
        if (isExtendedTraceEnabled)
        {
            TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
                TraceData.AttachToContext(
                    TraceData.Identify(this),
                    lastChance ? " (last chance)" : String.Empty),
                this);
        }
 
        TransferIsDeferred = true;
        bool attached = true;       // true if all child bindings have attached
        int count = MutableBindingExpressions.Count;
        for (int i = 0; i < count; ++i)
        {
            if (MutableBindingExpressions[i].StatusInternal == BindingStatusInternal.Unattached)
                attached = false;
        }
 
        // if the child bindings aren't ready yet, try again later.  Leave
        // TransferIsDeferred set, to indicate we're not ready yet.
        if (!attached && !lastChance)
        {
            if (isExtendedTraceEnabled)
            {
                TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
                    TraceData.ChildNotAttached(
                        TraceData.Identify(this)),
                    this);
            }
 
            return;
        }
 
        // listen to the Language property, if needed
        if (UsesLanguage)
        {
            WeakDependencySource[] commonSources = new WeakDependencySource[] { new WeakDependencySource(TargetElement, FrameworkElement.LanguageProperty) };
            WeakDependencySource[] newSources = CombineSources(-1, MutableBindingExpressions, MutableBindingExpressions.Count, null, commonSources);
            ChangeSources(newSources);
        }
 
        // initial transfer
        bool initialTransferIsUpdate = IsOneWayToSource;
        object currentValue;
        if (ShouldUpdateWithCurrentValue(target, out currentValue))
        {
            initialTransferIsUpdate = true;
            ChangeValue(currentValue, /*notify*/false);
            NeedsUpdate = true;
        }
 
        SetStatus(BindingStatusInternal.Active);
 
        if (!initialTransferIsUpdate)
        {
            UpdateTarget(false);
        }
        else
        {
            UpdateValue();
        }
    }
 
 
    //------------------------------------------------------
    //
    //  Public Properties
    //
    //------------------------------------------------------
 
    /// <summary>
    ///     The ValidationError that caused this
    ///     BindingExpression to be invalid.
    /// </summary>
    public override ValidationError ValidationError
    {
        get
        {
            ValidationError validationError = base.ValidationError;
 
            if (validationError == null)
            {
                for ( int i = 0; i < MutableBindingExpressions.Count; i++ )
                {
                    validationError = MutableBindingExpressions[i].ValidationError;
                    if (validationError != null)
                        break;
                }
            }
 
            return validationError;
        }
    }
 
    /// <summary>
    ///     HasError returns true if any of the ValidationRules
    ///     of any of its inner bindings failed its validation rule
    ///     or the Multi-/PriorityBinding itself has a failing validation rule.
    /// </summary>
    public override bool HasError
    {
        get
        {
            bool hasError = base.HasError;
 
            if (!hasError)
            {
                for ( int i = 0; i < MutableBindingExpressions.Count; i++ )
                {
                    if (MutableBindingExpressions[i].HasError)
                        return true;
                }
            }
 
            return hasError;
        }
    }
 
    /// <summary>
    ///     HasValidationError returns true if any of the ValidationRules
    ///     of any of its inner bindings failed its validation rule
    ///     or the Multi-/PriorityBinding itself has a failing validation rule.
    /// </summary>
    public override bool HasValidationError
    {
        get
        {
            bool hasError = base.HasValidationError;
 
            if (!hasError)
            {
                for ( int i = 0; i < MutableBindingExpressions.Count; i++ )
                {
                    if (MutableBindingExpressions[i].HasValidationError)
                        return true;
                }
            }
 
            return hasError;
        }
    }
 
    //------------------------------------------------------
    //
    //  Protected Internal Methods
    //
    //------------------------------------------------------
 
    /// <summary>
    ///     Attach a BindingExpression to the given target (element, property)
    /// </summary>
    /// <param name="d">DependencyObject being set</param>
    /// <param name="dp">Property being set</param>
    internal override bool AttachOverride(DependencyObject d, DependencyProperty dp)
    {
        if (!base.AttachOverride(d, dp))
            return false;
 
        DependencyObject target = TargetElement;
        if (target == null)
            return false;
 
        // listen for lost focus
        if (IsUpdateOnLostFocus)
        {
            LostFocusEventManager.AddHandler(target, OnLostFocus);
        }
 
        TransferIsDeferred = true;          // Defer data transfer until after we activate all the BindingExpressions
        int count = ParentMultiBinding.Bindings.Count;
        for (int i = 0; i < count; ++i)
        {
            // ISSUE: It may be possible to have _attachedBindingExpressions be non-zero
            // at the end of Detach if the conditions for the increment on Attach
            // and the decrement on Detach are not precisely the same.
            AttachBindingExpression(i, false); // create new binding and have it added to end
        }
 
        // attach to things that need tree context.  Do it synchronously
        // if possible, otherwise post a task.  This gives the parser et al.
        // a chance to assemble the tree before we start walking it.
        AttachToContext(false /* lastChance */);
        if (TransferIsDeferred)
        {
            Engine.AddTask(this, TaskOps.AttachToContext);
 
            if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.AttachToContext))
            {
                TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
                                    TraceData.DeferAttachToContext(
                                        TraceData.Identify(this)),
                                    this);
            }
        }
 
        return true;
    }
 
    /// <summary> sever all connections </summary>
    internal override void DetachOverride()
    {
        DependencyObject target = TargetElement;
        if (target != null && IsUpdateOnLostFocus)
        {
            LostFocusEventManager.RemoveHandler(target, OnLostFocus);
        }
 
        // Theoretically, we only need to detach number of AttentiveBindingExpressions,
        // but we'll traverse the whole list anyway and do aggressive clean-up.
        int count = MutableBindingExpressions.Count;
 
        for (int i = count - 1; i >= 0; i--)
        {
            BindingExpressionBase b = MutableBindingExpressions[i];
 
            if (b != null)
            {
                b.Detach();
                MutableBindingExpressions.RemoveAt(i);
            }
        }
 
        ChangeSources(null);
 
        base.DetachOverride();
    }
 
    /// <summary>
    /// Invalidate the given child expression.
    /// </summary>
    internal override void InvalidateChild(BindingExpressionBase bindingExpression)
    {
        int index = MutableBindingExpressions.IndexOf(bindingExpression);
 
        // do a sanity check that we care about this BindingExpression
        if (0 <= index && IsDynamic)
        {
            NeedsDataTransfer = true;
            Transfer();                 // this will Invalidate target property.
        }
    }
 
    /// <summary>
    /// Change the dependency sources for the given child expression.
    /// </summary>
    internal override void ChangeSourcesForChild(BindingExpressionBase bindingExpression, WeakDependencySource[] newSources)
    {
        int index = MutableBindingExpressions.IndexOf(bindingExpression);
 
        if (index >= 0)
        {
            WeakDependencySource[] commonSources = null;
            if (UsesLanguage)
            {
                commonSources = new WeakDependencySource[] { new WeakDependencySource(TargetElement, FrameworkElement.LanguageProperty) };
            }
 
            WeakDependencySource[] combinedSources = CombineSources(index, MutableBindingExpressions, MutableBindingExpressions.Count, newSources, commonSources);
            ChangeSources(combinedSources);
        }
    }
 
    /// <summary>
    /// Replace the given child expression with a new one.
    /// </summary>
    internal override void ReplaceChild(BindingExpressionBase bindingExpression)
    {
        int index = MutableBindingExpressions.IndexOf(bindingExpression);
        DependencyObject target = TargetElement;
 
        if (index >= 0 && target != null)
        {
            // detach and clean up the old binding
            bindingExpression.Detach();
 
            // replace BindingExpression
            AttachBindingExpression(index, true);
        }
    }
 
    // register the leaf bindings with the binding group
    internal override void UpdateBindingGroup(BindingGroup bg)
    {
        for (int i=0, n=MutableBindingExpressions.Count-1; i<n; ++i)
        {
            MutableBindingExpressions[i].UpdateBindingGroup(bg);
        }
    }
 
    /// <summary>
    /// Get the converted proposed value
    /// <summary>
    internal override object ConvertProposedValue(object value)
    {
        object result;
        bool success = ConvertProposedValueImpl(value, out result);
 
        // if the conversion failed, signal a validation error
        if (!success)
        {
            result = DependencyProperty.UnsetValue;
            ValidationError validationError = new ValidationError(ConversionValidationRule.Instance, this, SR.Format(SR.Validation_ConversionFailed, value), null);
            UpdateValidationError(validationError);
        }
 
        return result;
    }
 
    private bool ConvertProposedValueImpl(object value, out object result)
    {
        DependencyObject target = TargetElement;
        if (target == null)
        {
            result = DependencyProperty.UnsetValue;
            return false;
        }
 
        result = GetValuesForChildBindings(value);
 
        if (IsDetached)
        {
            return false;   // user code detached the binding.  give up.
        }
 
        if (result == DependencyProperty.UnsetValue)
        {
            SetStatus(BindingStatusInternal.UpdateSourceError);
 
            return false;
        }
 
        object[] values = (object[])result;
        if (values == null)
        {
            if (TraceData.IsEnabled)
            {
                TraceData.TraceAndNotify(TraceEventType.Error,
                    TraceData.BadMultiConverterForUpdate(
                        Converter.GetType().Name,
                        AvTrace.ToStringHelper(value),
                        AvTrace.TypeName(value)),
                    this);
            }
 
            result = DependencyProperty.UnsetValue;
            return false;
        }
 
        if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Update))
        {
            for (int i=0; i<values.Length; ++i)
            {
                TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
                                    TraceData.UserConvertBackMulti(
                                        TraceData.Identify(this),
                                        i,
                                        TraceData.Identify(values[i])),
                                    this);
            }
        }
 
        // if lengths are mismatched, show warning
        int count = MutableBindingExpressions.Count;
        if (values.Length != count && TraceData.IsEnabled)
        {
            TraceData.TraceAndNotify(TraceEventType.Information, TraceData.MultiValueConverterMismatch, this,
                traceParameters: new object[] { Converter.GetType().Name, count, values.Length, TraceData.DescribeTarget(target, TargetProperty) });
        }
 
        // use the smaller count
        if (values.Length < count)
            count = values.Length;
 
        // using the result of ConvertBack as the raw value, run each child binding
        // through the first two steps of the update/validate process
        bool success = true;
        for (int i = 0; i < count; ++i)
        {
            value = values[i];
 
            if (value != Binding.DoNothing && value != DependencyProperty.UnsetValue)
            {
                BindingExpressionBase bindExpr = MutableBindingExpressions[i];
 
                bindExpr.SetValue(target, TargetProperty, value);   // could pass (null, null, values[i])
 
                value = bindExpr.GetRawProposedValue();
                if (!bindExpr.Validate(value, ValidationStep.RawProposedValue))
                    value = DependencyProperty.UnsetValue;
 
                value = bindExpr.ConvertProposedValue(value);
            }
            else if (value == DependencyProperty.UnsetValue && TraceData.IsEnabled)
            {
                TraceData.TraceAndNotify(TraceEventType.Information,
                    TraceData.UnsetValueInMultiBindingExpressionUpdate(
                        Converter.GetType().Name,
                        AvTrace.ToStringHelper(value),
                        i,
                        _tempTypes[i]
                    ),
                    this);
            }
 
            if (value == DependencyProperty.UnsetValue)
            {
                success = false;
            }
 
            values[i] = value;
        }
 
        Array.Clear(_tempTypes, 0, _tempTypes.Length);
        result = values;
        return success;
    }
 
    object GetValuesForChildBindings(object rawValue)
    {
        if (Converter == null)
        {
            if (TraceData.IsEnabled)
            {
                TraceData.TraceAndNotify(TraceEventType.Error, TraceData.MultiValueConverterMissingForUpdate, this);
            }
 
            return DependencyProperty.UnsetValue;
        }
 
        CultureInfo culture = GetCulture();
        int count = MutableBindingExpressions.Count;
 
        for (int i = 0; i < count; ++i)
        {
            BindingExpressionBase bindExpr = MutableBindingExpressions[i];
            BindingExpression be = bindExpr as BindingExpression;
 
            if (be != null && be.UseDefaultValueConverter)
                _tempTypes[i] = be.ConverterSourceType;
            else
                _tempTypes[i] = TargetProperty.PropertyType;
        }
 
        // MultiValueConverters are always user-defined, so don't catch exceptions (bug 992237)
        return Converter.ConvertBack(rawValue, _tempTypes, ParentMultiBinding.ConverterParameter, culture);
    }
 
    /// <summary>
    /// Get the converted proposed value and inform the binding group
    /// <summary>
    internal override bool ObtainConvertedProposedValue(BindingGroup bindingGroup)
    {
        bool result = true;
        if (NeedsUpdate)
        {
            object value = bindingGroup.GetValue(this);
            if (value != DependencyProperty.UnsetValue)
            {
                object[] values;
                value = ConvertProposedValue(value);
 
                if (value == DependencyProperty.UnsetValue)
                {
                    result = false;
                }
                else if ((values = value as object[]) != null)
                {
                    for (int i=0; i<values.Length; ++i)
                    {
                        if (values[i] == DependencyProperty.UnsetValue)
                        {
                            result = false;
                        }
                    }
                }
            }
            StoreValueInBindingGroup(value, bindingGroup);
        }
        else
        {
            bindingGroup.UseSourceValue(this);
        }
 
        return result;
    }
 
    /// <summary>
    /// Update the source value
    /// <summary>
    internal override object UpdateSource(object convertedValue)
    {
        if (convertedValue == DependencyProperty.UnsetValue)
        {
            SetStatus(BindingStatusInternal.UpdateSourceError);
            return convertedValue;
        }
 
        object[] values = convertedValue as object[];
        int count = MutableBindingExpressions.Count;
        if (values.Length < count)
            count = values.Length;
 
        BeginSourceUpdate();
        bool updateActuallyHappened = false;
        for (int i = 0; i < count; ++i)
        {
            object value = values[i];
 
            if (value != Binding.DoNothing)
            {
                BindingExpressionBase bindExpr = MutableBindingExpressions[i];
 
                bindExpr.UpdateSource(value);
 
                if (bindExpr.StatusInternal == BindingStatusInternal.UpdateSourceError)
                {
                    SetStatus(BindingStatusInternal.UpdateSourceError);
                }
 
                updateActuallyHappened = true;
            }
        }
 
        if (!updateActuallyHappened)
        {
            IsInUpdate = false;     // inhibit the "$10 bug" re-fetch if nothing actually updated
        }
 
        EndSourceUpdate();
 
        OnSourceUpdated();
 
        return convertedValue;
    }
 
    /// <summary>
    /// Update the source value and inform the binding group
    /// <summary>
    internal override bool UpdateSource(BindingGroup bindingGroup)
    {
        bool result = true;
        if (NeedsUpdate)
        {
            object value = bindingGroup.GetValue(this);
            UpdateSource(value);
            if (value == DependencyProperty.UnsetValue)
            {
                result = false;
            }
        }
        return result;
    }
 
    /// <summary>
    /// Store the value in the binding group
    /// </summary>
    internal override void StoreValueInBindingGroup(object value, BindingGroup bindingGroup)
    {
        bindingGroup.SetValue(this, value);
 
        object[] values = value as object[];
        if (values != null)
        {
            int count = MutableBindingExpressions.Count;
            if (values.Length < count)
                count = values.Length;
 
            for (int i=0; i<count; ++i)
            {
                MutableBindingExpressions[i].StoreValueInBindingGroup(values[i], bindingGroup);
            }
        }
        else
        {
            for (int i=MutableBindingExpressions.Count-1; i>=0; --i)
            {
                MutableBindingExpressions[i].StoreValueInBindingGroup(DependencyProperty.UnsetValue, bindingGroup);
            }
        }
    }
 
    /// <summary>
    /// Run validation rules for the given step
    /// <summary>
    internal override bool Validate(object value, ValidationStep validationStep)
    {
        if (value == Binding.DoNothing)
            return true;
 
        if (value == DependencyProperty.UnsetValue)
        {
            SetStatus(BindingStatusInternal.UpdateSourceError);
            return false;
        }
 
        // run rules attached to this multibinding
        bool result = base.Validate(value, validationStep);
 
        // run rules attached to the child bindings
        switch (validationStep)
        {
            case ValidationStep.RawProposedValue:
                // the child bindings don't get raw values until the Convert step
                break;
 
            default:
                object[] values = value as object[];
                int count = MutableBindingExpressions.Count;
                if (values.Length < count)
                    count = values.Length;
 
                for (int i=0; i<count; ++i)
                {
                    value = values[i];
                    if (value == DependencyProperty.UnsetValue)
                    {
                        // an unset value means the binding failed validation at an earlier step,
                        // typically at Raw step, evaluated during the MultiBinding's ConvertValue
 
                        //result = false;
                        // COMPAT: This should mean the MultiBinding as a whole fails validation, but
                        // in 3.5 this didn't happen.  Instead the process continued, writing back
                        // values to child bindings that succeeded, and simply not writing back
                        // to child bindings that didn't.
                    }
                    else if (value != Binding.DoNothing)
                    {
                        if (!MutableBindingExpressions[i].Validate(value, validationStep))
                        {
                            values[i] = DependencyProperty.UnsetValue;  // prevent writing an invalid value
 
                            //result = false;
                            // COMPAT: as above, preserve v3.5 behavior by not failing when a
                            // child binding fails to validate
                        }
                    }
                }
                break;
        }
 
        return result;
    }
 
    /// <summary>
    /// Run validation rules for the given step, and inform the binding group
    /// <summary>
    internal override bool CheckValidationRules(BindingGroup bindingGroup, ValidationStep validationStep)
    {
        if (!NeedsValidation)
            return true;
 
        object value;
        switch (validationStep)
        {
            case ValidationStep.RawProposedValue:
            case ValidationStep.ConvertedProposedValue:
            case ValidationStep.UpdatedValue:
            case ValidationStep.CommittedValue:
                value = bindingGroup.GetValue(this);
                break;
            default:
                throw new InvalidOperationException(SR.Format(SR.ValidationRule_UnknownStep, validationStep, bindingGroup));
        }
 
        bool result = Validate(value, validationStep);
 
        if (result && validationStep == ValidationStep.CommittedValue)
        {
            NeedsValidation = false;
        }
 
        return result;
    }
 
    /// <summary>
    /// Get the proposed value(s) that would be written to the source(s), applying
    /// conversion and checking UI-side validation rules.
    /// </summary>
    internal override bool ValidateAndConvertProposedValue(out Collection<ProposedValue> values)
    {
        Debug.Assert(NeedsValidation, "check NeedsValidation before calling this");
        values = null;
 
        // validate raw proposed value
        object rawValue = GetRawProposedValue();
        bool isValid = Validate(rawValue, ValidationStep.RawProposedValue);
        if (!isValid)
        {
            return false;
        }
 
        // apply conversion
        object conversionResult = GetValuesForChildBindings(rawValue);
        if (IsDetached || conversionResult == DependencyProperty.UnsetValue || conversionResult == null)
        {
            return false;
        }
 
        int count = MutableBindingExpressions.Count;
        object[] convertedValues = (object[])conversionResult;
        if (convertedValues.Length < count)
            count = convertedValues.Length;
 
        values = new Collection<ProposedValue>();
        bool result = true;
 
        // validate child bindings
        for (int i = 0; i < count; ++i)
        {
            object value = convertedValues[i];
            if (value == Binding.DoNothing)
            {
            }
            else if (value == DependencyProperty.UnsetValue)
            {
                // conversion failure
                result = false;
            }
            else
            {
                // send converted value to child binding
                BindingExpressionBase bindExpr = MutableBindingExpressions[i];
                bindExpr.Value = value;
 
                // validate child binding
                if (bindExpr.NeedsValidation)
                {
                    Collection<ProposedValue> childValues;
                    bool childResult = bindExpr.ValidateAndConvertProposedValue(out childValues);
 
                    // append child's values to our values
                    if (childValues != null)
                    {
                        for (int k=0, n=childValues.Count; k<n; ++k)
                        {
                            values.Add(childValues[k]);
                        }
                    }
 
                    // merge child's result
                    result = result && childResult;
                }
            }
        }
 
        return result;
    }
 
 
    // Return the object from which the given value was obtained, if possible
    internal override object GetSourceItem(object newValue)
    {
        if (newValue == null)
            return null;        // this avoids false positive results
 
        // It's impossible to find the source item in the general case - the value
        // may have been produced by the multi-converter, combining inputs from
        // several different sources.   But we can do it in the special case where
        // one of the child bindings actually produced the final value, and the
        // converter merely selected it (or did other extraneous work).
        int count = MutableBindingExpressions.Count;
        for (int i = 0; i < count; ++i)
        {
            object value = MutableBindingExpressions[i].GetValue(null, null); // could pass (null, null)
            if (ItemsControl.EqualsEx(value, newValue))
                return MutableBindingExpressions[i].GetSourceItem(newValue);
        }
 
        return null;
    }
 
 
    //------------------------------------------------------
    //
    //  Private Properties
    //
    //------------------------------------------------------
 
    /// <summary>
    /// expose a mutable version of the list of all BindingExpressions;
    /// derived internal classes need to be able to populate this list
    /// </summary>
    private Collection<BindingExpressionBase> MutableBindingExpressions
    {
        get { return _list; }
    }
 
    IMultiValueConverter Converter
    {
        get { return _converter; }
        set { _converter = value; }
    }
 
    //------------------------------------------------------
    //
    //  Private Methods
    //
    //------------------------------------------------------
 
    // Create a BindingExpression for position i
    BindingExpressionBase AttachBindingExpression(int i, bool replaceExisting)
    {
        DependencyObject target = TargetElement;
        if (target == null)
            return null;
 
        BindingBase binding = ParentMultiBinding.Bindings[i];
 
        // Check if replacement bindings have the correct UpdateSourceTrigger
        MultiBinding.CheckTrigger(binding);
 
        BindingExpressionBase bindExpr = binding.CreateBindingExpression(target, TargetProperty, this);
        if (replaceExisting) // replace exisiting or add as new binding?
            MutableBindingExpressions[i] = bindExpr;
        else
            MutableBindingExpressions.Add(bindExpr);
 
        bindExpr.Attach(target, TargetProperty);
        return bindExpr;
    }
 
    internal override void HandlePropertyInvalidation(DependencyObject d, DependencyPropertyChangedEventArgs args)
    {
        DependencyProperty dp = args.Property;
 
        if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Events))
        {
            TraceData.TraceAndNotify(TraceEventType.Warning,
                                TraceData.GotPropertyChanged(
                                    TraceData.Identify(this),
                                    TraceData.Identify(d),
                                    dp.Name));
        }
 
        bool isConnected = true;
        TransferIsDeferred = true;
 
        if (UsesLanguage && d == TargetElement && dp == FrameworkElement.LanguageProperty)
        {
            InvalidateCulture();
            NeedsDataTransfer = true;   // force a transfer - it will honor the new culture
        }
 
        // if the binding has been detached (by the reference to TargetElement), quit now
        if (IsDetached)
            return;
 
        int n = MutableBindingExpressions.Count;
        for (int i = 0; i < n; ++i)
        {
            BindingExpressionBase bindExpr = MutableBindingExpressions[i];
            if (bindExpr != null)
            {
                DependencySource[] sources = bindExpr.GetSources();
 
                if (sources != null)
                {
                    for (int j = 0; j < sources.Length; ++j)
                    {
                        DependencySource source = sources[j];
 
                        if (source.DependencyObject == d && source.DependencyProperty == dp)
                        {
                            bindExpr.OnPropertyInvalidation(d, args);
                            break;
                        }
                    }
                }
 
                if (bindExpr.IsDisconnected)
                {
                    isConnected = false;
                }
            }
        }
 
        TransferIsDeferred = false;
 
        if (isConnected)
        {
            Transfer();                 // Transfer if inner BindingExpressions have called Invalidate(binding)
        }
        else
        {
            Disconnect();
        }
    }
 
    /// <summary>
    /// Handle events from the centralized event table
    /// </summary>
    internal override bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
    {
            return false;   // this method is no longer used (but must remain, for compat)
    }
 
    internal override void OnLostFocus(object sender, RoutedEventArgs e)
    {
        if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Events))
        {
            TraceData.TraceAndNotify(TraceEventType.Warning,
                                TraceData.GotEvent(
                                    TraceData.Identify(this),
                                    "LostFocus",
                                    TraceData.Identify(sender)));
        }
 
        Update();
    }
 
#region Value
 
    /// <summary> Force a data transfer from source(s) to target </summary>
    /// <param name="includeInnerBindings">
    ///     use true to propagate UpdateTarget call to all inner BindingExpressions;
    ///     use false to avoid forcing data re-transfer from one-time inner BindingExpressions
    /// </param>
    void UpdateTarget(bool includeInnerBindings)
    {
        TransferIsDeferred = true;
 
        if (includeInnerBindings)
        {
            foreach (BindingExpressionBase b in MutableBindingExpressions)
            {
                b.UpdateTarget();
            }
        }
 
        TransferIsDeferred = false;
        NeedsDataTransfer = true;   // force data transfer
        Transfer();
 
        NeedsUpdate = false;
    }
 
    // transfer a value from the source to the target
    void Transfer()
    {
        // required state for transfer
        if (    NeedsDataTransfer       // Transfer is needed
            &&  StatusInternal != BindingStatusInternal.Unattached  // All bindings are attached
            &&  !TransferIsDeferred)    // Not aggregating transfers
        {
            TransferValue();
        }
    }
 
    // transfer a value from the source to the target
    void TransferValue()
    {
        IsInTransfer = true;
        NeedsDataTransfer = false;
 
        DependencyObject target = TargetElement;
        if (target == null)
            goto Done;
 
        bool isExtendedTraceEnabled = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Transfer);
 
        object value = DependencyProperty.UnsetValue;
        object preFormattedValue = _tempValues;
        CultureInfo culture = GetCulture();
 
        // gather values from inner BindingExpressions
        int count = MutableBindingExpressions.Count;
        for (int i = 0; i < count; ++i)
        {
            _tempValues[i] = MutableBindingExpressions[i].GetValue(target, TargetProperty); // could pass (null, null)
 
            if (isExtendedTraceEnabled)
            {
                TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
                                    TraceData.GetRawValueMulti(
                                        TraceData.Identify(this),
                                        i,
                                        TraceData.Identify(_tempValues[i])),
                                    this);
            }
        }
 
        // apply the converter
        if (Converter != null)
        {
            // MultiValueConverters are always user-defined, so don't catch exceptions (bug 992237)
            preFormattedValue = Converter.Convert(_tempValues, TargetProperty.PropertyType, ParentMultiBinding.ConverterParameter, culture);
 
            if (IsDetached)
            {
                // user code detached the binding.  Give up.
                return;
            }
 
            if (isExtendedTraceEnabled)
            {
                TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
                                    TraceData.UserConverter(
                                        TraceData.Identify(this),
                                        TraceData.Identify(preFormattedValue)),
                                    this);
            }
        }
        else if (EffectiveStringFormat != null)
        {
            // preFormattedValue = _tempValues;
            // But check for child binding conversion errors
            for (int i=0; i<_tempValues.Length; ++i)
            {
                if (_tempValues[i] == DependencyProperty.UnsetValue)
                {
                    preFormattedValue = DependencyProperty.UnsetValue;
                    break;
                }
            }
        }
        else    // no converter (perhaps user specified it in error)
        {
            if (TraceData.IsEnabled)
            {
                TraceData.TraceAndNotify(TraceEventType.Error, TraceData.MultiValueConverterMissingForTransfer, this);
            }
 
            goto Done;
        }
 
        // apply string formatting
        if (EffectiveStringFormat == null || preFormattedValue == Binding.DoNothing || preFormattedValue == DependencyProperty.UnsetValue)
        {
            value = preFormattedValue;
        }
        else
        {
            try
            {
                // we call String.Format either with multiple values (obtained from
                // the child bindings) or a single value (as produced by the converter).
                // The if-test is needed to avoid wrapping _tempValues inside another object[].
                if (preFormattedValue == _tempValues)
                {
                    value = String.Format(culture, EffectiveStringFormat, _tempValues);
                }
                else
                {
                    value = String.Format(culture, EffectiveStringFormat, preFormattedValue);
                }
 
                if (isExtendedTraceEnabled)
                {
                    TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
                                        TraceData.FormattedValue(
                                            TraceData.Identify(this),
                                            TraceData.Identify(value)),
                                        this);
                }
            }
            catch (FormatException)
            {
                // formatting didn't work
                value = DependencyProperty.UnsetValue;
 
                if (isExtendedTraceEnabled)
                {
                    TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
                                    TraceData.FormattingFailed(
                                        TraceData.Identify(this),
                                        EffectiveStringFormat),
                                    this);
                }
            }
        }
 
        Array.Clear(_tempValues, 0, _tempValues.Length);
 
        // the special value DoNothing means no error, but no data transfer
        if (value == Binding.DoNothing)
            goto Done;
 
        // ultimately, TargetNullValue should get assigned implicitly,
        // even if the user doesn't declare it.  We can't do this yet because
        // of back-compat.  I wrote it both ways, and #if'd out the breaking
        // change.
    #if TargetNullValueBC   //BreakingChange
        if (IsNullValue(value))
    #else
        if (EffectiveTargetNullValue != DependencyProperty.UnsetValue &&
            IsNullValue(value))
    #endif
        {
            value = EffectiveTargetNullValue;
 
            if (isExtendedTraceEnabled)
            {
                TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
                                    TraceData.NullConverter(
                                        TraceData.Identify(this),
                                        TraceData.Identify(value)),
                                    this);
            }
        }
 
        // if the value isn't acceptable to the target property, don't use it
        if (value != DependencyProperty.UnsetValue && !TargetProperty.IsValidValue(value))
        {
            if (TraceData.IsEnabled)
            {
                TraceData.TraceAndNotify(TraceLevel, TraceData.BadValueAtTransfer, this,
                    traceParameters: new object[] { value, this });
            }
 
            if (isExtendedTraceEnabled)
            {
                    TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
                                    TraceData.BadValueAtTransferExtended(
                                        TraceData.Identify(this),
                                        TraceData.Identify(value)),
                                    this);
            }
 
            value = DependencyProperty.UnsetValue;
        }
 
        // if we can't obtain a value, try the fallback value.
        if (value == DependencyProperty.UnsetValue)
        {
            value = UseFallbackValue();
 
            if (isExtendedTraceEnabled)
            {
                TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
                                TraceData.UseFallback(
                                    TraceData.Identify(this),
                                    TraceData.Identify(value)),
                                this);
            }
        }
 
        if (isExtendedTraceEnabled)
        {
            TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
                            TraceData.TransferValue(
                                TraceData.Identify(this),
                                TraceData.Identify(value)),
                            this);
        }
 
        // if this is a re-transfer after a source update and the value
        // hasn't changed, don't do any more work.
        bool realTransfer = !(IsInUpdate && ItemsControl.EqualsEx(value, Value));
 
        if (realTransfer)
        {
            // update the cached value
            ChangeValue(value, true);
 
            // push the new value through the property engine
            Invalidate(false);
 
            Validation.ClearInvalid(this);
        }
 
        // after updating all the state (value, validation), mark the binding clean
        Clean();
 
        if (realTransfer)
        {
            OnTargetUpdated();
        }
 
    Done:
        IsInTransfer = false;
    }
 
    void OnTargetUpdated()
    {
        if (NotifyOnTargetUpdated)
        {
            DependencyObject target = TargetElement;
            if (target != null)
            {
                // while attaching a normal (not style-defined) BindingExpression,
                // we must defer raising the event until after the
                // property has been invalidated, so that the event handler
                // gets the right value if it asks (bug 1036862)
                if (IsAttaching && this == target.ReadLocalValue(TargetProperty))
                {
                    Engine.AddTask(this, TaskOps.RaiseTargetUpdatedEvent);
                }
                else
                {
                    BindingExpression.OnTargetUpdated(target, TargetProperty);
                }
            }
        }
    }
 
    void OnSourceUpdated()
    {
        if (NotifyOnSourceUpdated)
        {
            DependencyObject target = TargetElement;
            if (target != null)
            {
                BindingExpression.OnSourceUpdated(target, TargetProperty);
            }
        }
    }
 
    internal override bool ShouldReactToDirtyOverride()
    {
        // react only if all the child bindings should react
        foreach (BindingExpressionBase beb in MutableBindingExpressions)
        {
            if (!beb.ShouldReactToDirtyOverride())
            {
                return false;
            }
        }
        return true;
    }
 
    // transfer a value from the target to the source
    internal override bool UpdateOverride()
    {
        // various reasons not to update:
        if (   !NeedsUpdate                     // nothing to do
            || !IsReflective                    // no update desired
            || IsInTransfer                     // in a transfer
            || StatusInternal == BindingStatusInternal.Unattached // not ready yet
            )
            return true;
 
        return UpdateValue();
    }
 
#endregion Value
 
    //------------------------------------------------------
    //
    //  Private Fields
    //
    //------------------------------------------------------
 
    Collection<BindingExpressionBase>  _list = new Collection<BindingExpressionBase>();
    IMultiValueConverter    _converter;
    object[]                _tempValues;
    Type[]                  _tempTypes;
}
}