// Given a multi data trigger and associated context information,
        //  evaluate the old and new states of the trigger.

        // The short-circuit logic of multi property trigger applies here too.
        //  we bail if any of the "other" conditions evaluate to false.
        private static void EvaluateOldNewStates( MultiDataTrigger multiDataTrigger,
            DependencyObject triggerContainer,
            BindingBase binding, BindingValueChangedEventArgs changedArgs, UncommonField<HybridDictionary[]> dataField,
            Style style, FrameworkTemplate frameworkTemplate,
            out bool oldState, out bool newState )
        {
            BindingBase evaluationBinding = null;
            object  evaluationValue = null;
            TriggerCondition[] conditions = multiDataTrigger.TriggerConditions;

            // Set up the default condition: A trigger with no conditions will never evaluate to true.
            oldState = false;
            newState = false;

            for( int i = 0; i < multiDataTrigger.Conditions.Count; i++ )
            {
                evaluationBinding = conditions[i].Binding;

                Debug.Assert( evaluationBinding != null,
                    "A null binding was encountered in a MultiDataTrigger conditions collection - this is invalid input and should have been caught earlier.");

                if( evaluationBinding == binding )
                {
                    // The binding that changed belonged to the current condition.
                    oldState = conditions[i].ConvertAndMatch(changedArgs.OldValue);
                    newState = conditions[i].ConvertAndMatch(changedArgs.NewValue);

                    if( oldState == newState )
                    {
                        // There couldn't possibly be a transition here, abort.  The
                        //  returned values here aren't necessarily the state of the
                        //  triggers, but we only care about a transition today.  If
                        //  we care about actual values, we'll need to continue evaluation.
                        return;
                    }
                }
                else
                {
                    evaluationValue = GetDataTriggerValue(dataField, triggerContainer, evaluationBinding);
                    if( !conditions[i].ConvertAndMatch(evaluationValue) )
                    {
                        // A condition other than the one changed has evaluated to false.
                        // There couldn't possibly be a transition here, short-circuit and abort.
                        oldState = false;
                        newState = false;
                        return;
                    }
                }
            }

            // We should only get this far only if the binding change causes
            //  a true->false (or vice versa) transition in one of the conditions,
            // AND that every other condition evaluated to true.
            return;
        }
        // Given a Style/Template and a Binding whose value has changed, look for
        //  any data triggers that have trigger actions (EnterAction/ExitAction)
        //  and see if any of those actions need to run as a response to this change.
        private static void InvokeApplicableDataTriggerActions(
            Style                               style,
            FrameworkTemplate                   frameworkTemplate,
            DependencyObject                    container,
            BindingBase                         binding,
            BindingValueChangedEventArgs        e,
            UncommonField<HybridDictionary[]>   dataField)
        {
            HybridDictionary dataTriggersWithActions;

            if( style != null )
            {
                dataTriggersWithActions = style.DataTriggersWithActions;
            }
            else if( frameworkTemplate != null )
            {
                dataTriggersWithActions = frameworkTemplate.DataTriggersWithActions;
            }
            else
            {
                dataTriggersWithActions = null;
            }

            if( dataTriggersWithActions != null )
            {
                object candidateTrigger = dataTriggersWithActions[binding];

                if( candidateTrigger != null )
                {
                    // One or more trigger objects need to be evaluated.  The candidateTrigger
                    //  object may be a single trigger or a collection of them.
                    TriggerBase triggerBase = candidateTrigger as TriggerBase;
                    if( triggerBase != null )
                    {
                        InvokeDataTriggerActions( triggerBase, container, binding, e,
                            style, frameworkTemplate, dataField);
                    }
                    else
                    {
                        Debug.Assert(candidateTrigger is List<TriggerBase>, "Internal data structure error: The HybridDictionary [Style/Template].DataTriggersWithActions " +
                            "is expected to hold a single TriggerBase or a List<T> of them.  An object of type " +
                            candidateTrigger.GetType().ToString() + " is not expected.  Where did this object come from?");

                        List<TriggerBase> triggerList = (List<TriggerBase>)candidateTrigger;

                        for( int i = 0; i < triggerList.Count; i++ )
                        {
                            InvokeDataTriggerActions( triggerList[i], container, binding, e,
                                style, frameworkTemplate, dataField);
                        }
                    }
                }
            }
        }
        //
        //  This method
        //  1. Is Invoked when a binding in a condition of a data trigger changes its value.
        //  2. When this happens we must invalidate all its dependents
        //
        private static void OnBindingValueInThemeStyleChanged(object sender, BindingValueChangedEventArgs e)
        {
            BindingExpressionBase bindingExpr = (BindingExpressionBase)sender;
            BindingBase binding = bindingExpr.ParentBindingBase;
            DependencyObject container = bindingExpr.TargetElement;

            FrameworkElement fe;
            FrameworkContentElement fce;
            Helper.DowncastToFEorFCE(container, out fe, out fce, false);
            Style style = (fe != null) ? fe.ThemeStyle : fce.ThemeStyle;

            // Look for data trigger Setter information - invalidate the associated
            //  properties if found.
            HybridDictionary dataTriggerRecordFromBinding = style._dataTriggerRecordFromBinding;

            if( dataTriggerRecordFromBinding != null &&
                !bindingExpr.IsAttaching ) // Don't invalidate in the middle of attaching - effective value will be updated elsewhere in Style application code.
            {
                DataTriggerRecord record = (DataTriggerRecord)dataTriggerRecordFromBinding[binding];
                if (record != null)         // triggers with no setters (only actions) don't appear in the table
                {
                    InvalidateDependents(style, null, container, null, ref record.Dependents, false);
                }
            }

            // Look for any applicable trigger EnterAction or ExitAction
            InvokeApplicableDataTriggerActions(style, null, container, binding, e, ThemeStyleDataField);
        }
        // Given a single data trigger and associated context information,
        //  evaluate the old and new states of the trigger.
        private static void EvaluateOldNewStates( DataTrigger dataTrigger,
            DependencyObject triggerContainer,
            BindingBase binding, BindingValueChangedEventArgs bindingChangedArgs, UncommonField<HybridDictionary[]> dataField,
            Style style, FrameworkTemplate frameworkTemplate,
            out bool oldState, out bool newState )
        {
            TriggerCondition[] conditions = dataTrigger.TriggerConditions;
            Debug.Assert( conditions != null && conditions.Length == 1,
                "This method assumes there is exactly one TriggerCondition." );

            oldState = conditions[0].ConvertAndMatch(bindingChangedArgs.OldValue);
            newState = conditions[0].ConvertAndMatch(bindingChangedArgs.NewValue);
        }
        // This is expected to be called when a Binding for a DataTrigger has
        //   changed and we need to look for true/false transitions.  If found,
        //   invoke EnterAction (or ExitAction) as needed.
        private static void InvokeDataTriggerActions( TriggerBase triggerBase,
            DependencyObject triggerContainer, BindingBase binding, BindingValueChangedEventArgs bindingChangedArgs,
            Style style, FrameworkTemplate frameworkTemplate,
            UncommonField<HybridDictionary[]> dataField)
        {
            bool oldState;
            bool newState;

            DataTrigger dataTrigger = triggerBase as DataTrigger;

            if( dataTrigger != null )
            {
                EvaluateOldNewStates( dataTrigger, triggerContainer,
                    binding, bindingChangedArgs, dataField,
                    style, frameworkTemplate,
                    out oldState, out newState );
            }
            else
            {
                Debug.Assert( triggerBase is MultiDataTrigger,
                    "This method only handles DataTrigger and MultiDataTrigger.  Other types should have been handled elsewhere." );

                EvaluateOldNewStates( (MultiDataTrigger)triggerBase, triggerContainer,
                    binding, bindingChangedArgs, dataField,
                    style, frameworkTemplate,
                    out oldState, out newState );
            }

            InvokeEnterOrExitActions( triggerBase, oldState, newState, triggerContainer,
                style, frameworkTemplate  );
        }