// 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 ); }