private static AnimatedWrapPanelAttachedData GetAnimatedWrapPanelAttachedData(DependencyObject obj) { // This uses a standard attached DP lazy-init pattern that will create the default value // for this property and set it if GetValue returned null. That means that this method // will never return null. object value = obj.GetValue(AnimatedWrapPanelAttachedDataProperty); if (value == null) { AnimatedWrapPanelAttachedData data = new AnimatedWrapPanelAttachedData(); SetAnimatedWrapPanelAttachedData(obj, data); return(data); } else { return((AnimatedWrapPanelAttachedData)value); } }
/// <summary> /// A "normal" ArrangeOverride would just put things where they belong. What this one does /// is to move the children towards their destinations according to the virtual animation /// data that has been attached to each element. When they get there, the virtual animation /// is turned off. /// </summary> protected override Size ArrangeOverride(Size finalSize) { DateTime now = DateTime.Now; foreach (UIElement child in Children) { AnimatedWrapPanelAttachedData data = GetAnimatedWrapPanelAttachedData(child); TimeSpan elapsed = data.GetElapsed(now); if (elapsed < Duration || data.TargetPosition != data.CurrentPosition) { // The virtual animation is not done yet, so figure out how far along it is... double progress = (Duration.TimeSpan != TimeSpan.Zero) ? Math.Min(elapsed.TotalMilliseconds / Duration.TimeSpan.TotalMilliseconds, 1.0) : 1; // ...and what the next position is. Point newPosition = BlendPoint(_interpolation, data.StartPosition, data.TargetPosition, progress); child.Arrange(new Rect(newPosition.X, newPosition.Y, child.DesiredSize.Width, _rowHeights[data.Row])); data.CurrentPosition = newPosition; } else { // This element is not animating, but it might have become invalid on its own, so it still // needs to be arranged. The layout system will do as little as possible. child.Arrange(new Rect(data.CurrentPosition.X, data.CurrentPosition.Y, child.DesiredSize.Width, _rowHeights[data.Row])); if (data.IsAnimating) { --_animatingElements; // This is the only place where IsAnimating is set to false. This turns off the virtual animation. data.IsAnimating = false; } } } return(finalSize); }
private static void SetAnimatedWrapPanelAttachedData(DependencyObject obj, AnimatedWrapPanelAttachedData value) { obj.SetValue(AnimatedWrapPanelAttachedDataProperty, value); }
/// <summary> /// This method is required when subclassing from Panel. It figures out how big the childre are /// and where they should go. It attaches an object to each child to hold the data so that the /// panel itself does not have to maintain state. /// </summary> protected override Size MeasureOverride(Size availableSize) { // Measure each child first. This is inefficient, but it is necessary in order // to have smooth animations of new children. Otherwise, the animations may jump // due to the time that template expansion can take. foreach (FrameworkElement child in Children) { child.Measure(availableSize); } double rowHeight = 0; int row = 0; _rowHeights.Clear(); Size desiredSize = Size.Empty; Point nextChildPosition = new Point(0, 0); DateTime now = DateTime.Now; _animatingElements = 0; // Now each the position from each child is computed. If the child is not where it is supposed // to be, set up a virtual animation to move it there. foreach (FrameworkElement child in Children) { if (nextChildPosition.X + child.DesiredSize.Width > availableSize.Width) { // Save old row information _rowHeights.Add(rowHeight); ++row; nextChildPosition.X = 0; nextChildPosition.Y += rowHeight; rowHeight = 0; } AnimatedWrapPanelAttachedData data = GetAnimatedWrapPanelAttachedData(child); // If this is a new element, then start it off of the screen if (data.CurrentPosition == AnimatedWrapPanelAttachedData.Unset) { data.CurrentPosition = new Point(-child.DesiredSize.Width, -child.DesiredSize.Height); } if (data.TargetPosition != nextChildPosition) { // The target of this element is either brand new or has moved, so we need to // recalculate everything, and set up a virtual animation. data.StartTime = now; data.StartPosition = data.CurrentPosition; data.TargetPosition = nextChildPosition; data.Row = row; // IsAnimating only gets set to true right here. This begins the virtual animation // for this element. data.IsAnimating = true; ++_animatingElements; } else if (data.IsAnimating) { // This item is still animating, so keep track of it, but since // the target position has not changed, don't do anything else. ++_animatingElements; } desiredSize.Width = Math.Max(desiredSize.Width, nextChildPosition.X + child.DesiredSize.Width); desiredSize.Height = Math.Max(desiredSize.Height, nextChildPosition.Y + child.DesiredSize.Height); // Keep track of the maximum height for this line. rowHeight = Math.Max(rowHeight, child.DesiredSize.Height); // Advance the position of the next element. nextChildPosition.X += child.DesiredSize.Width; } _rowHeights.Add(rowHeight); // Debug.WriteLine("Animating {0} elements", _animatingElements); return(desiredSize); }