in src/Microsoft.Xaml.Behaviors/Core/ExtendedVisualStateManager.cs [244:431]
protected override bool GoToStateCore(FrameworkElement control, FrameworkElement stateGroupsRoot, string stateName, VisualStateGroup group, VisualState state, bool useTransitions)
{
//
// Reminder that a layout transition may already be running; several of these functions keep track of the current value of MovingElements
// so that they can account for the fact that these elements have unusual layout positions right now.
//
Storyboard layoutStoryboard;
// On WPF 4 there's an open bug (882549) where platform controls reassert all states every measure, and at designtime this is a problem because the CommonStates
// can't possibly be right. Fix is to inhibit a state change when we are in the middle of setting up a FluidLayout change.
if (this.changingState)
{
return false;
}
if (group == null || state == null)
{
return false;
}
//
// Keep our own copy of the current state
//
VisualState previousState = GetCurrentState(group);
if (previousState == state)
{
return true;
}
//
// Find the transition that should be used
//
VisualTransition transition = FindTransition(group, previousState, state);
bool animateWithTransitionEffect = PrepareTransitionEffectImage(stateGroupsRoot, useTransitions, transition);
//
// If this group is not using Fluid Layout, then get out
//
if (!GetUseFluidLayout(group))
{
return this.TransitionEffectAwareGoToStateCore(control, stateGroupsRoot, stateName, group, state, useTransitions, transition, animateWithTransitionEffect, previousState);
}
//
// Get all layout properties out of the state's storyboard. This is only performed once per state thanks to an attached property on the state.
//
layoutStoryboard = ExtractLayoutStoryboard(state);
//
// Make sure that we have a place to store the original values for anything that we might overwrite
//
List<OriginalLayoutValueRecord> originalValueRecords = GetOriginalLayoutValues(group);
if (originalValueRecords == null)
{
originalValueRecords = new List<OriginalLayoutValueRecord>();
SetOriginalLayoutValues(group, originalValueRecords);
}
//
// Take the easy road if we don't have to animate - this is a compressed version of what's below
//
if (!useTransitions)
{
if (LayoutTransitionStoryboard != null)
{
StopAnimations();
}
bool returnValue = this.TransitionEffectAwareGoToStateCore(control, stateGroupsRoot, stateName, group, state, useTransitions, transition, animateWithTransitionEffect, previousState);
SetLayoutStoryboardProperties(control, stateGroupsRoot, layoutStoryboard, originalValueRecords);
return returnValue;
}
if (layoutStoryboard.Children.Count == 0 && originalValueRecords.Count == 0)
{
return this.TransitionEffectAwareGoToStateCore(control, stateGroupsRoot, stateName, group, state, useTransitions, transition, animateWithTransitionEffect, previousState);
}
try
{
this.changingState = true;
// Force layout to be updated first - helps with OnLoaded() animations.
stateGroupsRoot.UpdateLayout();
//
// Enumerate elements in the state (and the previous state), then expand that list to contain anything that might move
// as a result of these elements changing size:
// - siblings of elements in the list
// - parents of elements in the list
// - siblings of parents in the list
// - grandparents and their siblings
// - etc.
// - no need to travel *down* the tree, if a parent changes size then the children will move
//
List<FrameworkElement> targetElements = FindTargetElements(control, stateGroupsRoot, layoutStoryboard, originalValueRecords, MovingElements);
//
// Get the parent-relative rect of every element in the list, and the original effective opacity (= opacity * visibility, more or less)
// - Assume that every Visibility change is an intended animation, unlike the work we do to filter the set of elements that actually moved
//
Dictionary<FrameworkElement, Rect> oldRects = GetRectsOfTargets(targetElements, MovingElements);
Dictionary<FrameworkElement, double> oldOpacities = GetOldOpacities(control, stateGroupsRoot, layoutStoryboard, originalValueRecords, MovingElements);
//
// Now that we've captured the current situation, stop the previous transition before going to the new state
//
if (LayoutTransitionStoryboard != null)
{
stateGroupsRoot.LayoutUpdated -= new EventHandler(control_LayoutUpdated);
StopAnimations();
stateGroupsRoot.UpdateLayout();
}
//
// Go to the new state; jump immediately to the layout changes
//
this.TransitionEffectAwareGoToStateCore(control, stateGroupsRoot, stateName, group, state, useTransitions, transition, animateWithTransitionEffect, previousState);
SetLayoutStoryboardProperties(control, stateGroupsRoot, layoutStoryboard, originalValueRecords);
//
// UpdateLayout
//
stateGroupsRoot.UpdateLayout();
//
// Get the parent-relative rect of every element in the list
// - Note: Do not need the new visibility since we can just read the property
//
Dictionary<FrameworkElement, Rect> newRects = GetRectsOfTargets(targetElements, null);
//
// Compute the set of elements from the list whose rects changed
//
MovingElements = new List<FrameworkElement>();
foreach (FrameworkElement target in targetElements)
{
if (oldRects[target] != newRects[target])
{
MovingElements.Add(target);
}
}
//
// Add the elements whose opacity is changing, so we can change opacity on the wrapper and not the element
//
foreach (FrameworkElement visibilityElement in oldOpacities.Keys)
{
if (!MovingElements.Contains(visibilityElement))
{
MovingElements.Add(visibilityElement);
}
}
//
// Freeze these elements at their current location, otherwise detach from layout
// - For now, wrap each of these elements in a Canvas
//
WrapMovingElementsInCanvases(MovingElements, oldRects, newRects);
stateGroupsRoot.LayoutUpdated += new EventHandler(control_LayoutUpdated);
//
// Animate the size/location of these elements from old rect to new rect
// - NOT with scale transforms, though translate transforms are OK
// - changing the size of the element should call measure/arrange on any of its children that are not themselves detached from layout
//
LayoutTransitionStoryboard = CreateLayoutTransitionStoryboard(transition, MovingElements, oldOpacities);
LayoutTransitionStoryboard.Completed += (EventHandler)delegate (object sender, EventArgs args)
{
stateGroupsRoot.LayoutUpdated -= new EventHandler(control_LayoutUpdated);
StopAnimations();
};
LayoutTransitionStoryboard.Begin();
}
finally
{
this.changingState = false;
}
return true;
}