Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Framework / System / Windows / Controls / Stack.cs / 1 / Stack.cs
//----------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
// File: Stack.cs
//
// Description: Implementation of StackPanel class.
// Spec at http://avalon/layout/Specs/StackPanel.doc
//
// History:
// 06/25/2004 : olego - Created
// 09/03/2004 : greglett - Converted to new layout system, IScrollInfo implementation
//
//---------------------------------------------------------------------------
//#define Profiling
using MS.Internal;
using MS.Utility;
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Threading;
namespace System.Windows.Controls
{
///
/// StackPanel is used to arrange children into single line.
///
public class StackPanel : Panel, IScrollInfo
{
//-------------------------------------------------------------------
//
// Constructors
//
//-------------------------------------------------------------------
#region Constructors
///
/// Default constructor.
///
public StackPanel() : base()
{
}
#endregion Constructors
//--------------------------------------------------------------------
//
// Public Methods
//
//-------------------------------------------------------------------
#region Public Methods
//------------------------------------------------------------
// IScrollInfo Methods
//------------------------------------------------------------
#region IScrollInfo Methods
///
/// Scroll content by one line to the top.
///
public void LineUp()
{
SetVerticalOffset(VerticalOffset - ((Orientation == Orientation.Vertical) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one line to the bottom.
///
public void LineDown()
{
SetVerticalOffset(VerticalOffset + ((Orientation == Orientation.Vertical) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one line to the left.
///
public void LineLeft()
{
SetHorizontalOffset(HorizontalOffset - ((Orientation == Orientation.Horizontal) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one line to the right.
///
public void LineRight()
{
SetHorizontalOffset(HorizontalOffset + ((Orientation == Orientation.Horizontal) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one page to the top.
///
public void PageUp()
{
SetVerticalOffset(VerticalOffset - ViewportHeight);
}
///
/// Scroll content by one page to the bottom.
///
public void PageDown()
{
SetVerticalOffset(VerticalOffset + ViewportHeight);
}
///
/// Scroll content by one page to the left.
///
public void PageLeft()
{
SetHorizontalOffset(HorizontalOffset - ViewportWidth);
}
///
/// Scroll content by one page to the right.
///
public void PageRight()
{
SetHorizontalOffset(HorizontalOffset + ViewportWidth);
}
///
/// Scroll content by one page to the top.
///
public void MouseWheelUp()
{
SetVerticalOffset(VerticalOffset - SystemParameters.WheelScrollLines * ((Orientation == Orientation.Vertical) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one page to the bottom.
///
public void MouseWheelDown()
{
SetVerticalOffset(VerticalOffset + SystemParameters.WheelScrollLines * ((Orientation == Orientation.Vertical) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one page to the left.
///
public void MouseWheelLeft()
{
SetHorizontalOffset(HorizontalOffset - 3.0 * ((Orientation == Orientation.Horizontal) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one page to the right.
///
public void MouseWheelRight()
{
SetHorizontalOffset(HorizontalOffset + 3.0 * ((Orientation == Orientation.Horizontal) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Set the HorizontalOffset to the passed value.
///
public void SetHorizontalOffset(double offset)
{
EnsureScrollData();
double scrollX = ScrollContentPresenter.ValidateInputOffset(offset, "HorizontalOffset");
if (!DoubleUtil.AreClose(scrollX, _scrollData._offset.X))
{
_scrollData._offset.X = scrollX;
InvalidateMeasure();
}
}
///
/// Set the VerticalOffset to the passed value.
///
public void SetVerticalOffset(double offset)
{
EnsureScrollData();
double scrollY = ScrollContentPresenter.ValidateInputOffset(offset, "VerticalOffset");
if (!DoubleUtil.AreClose(scrollY, _scrollData._offset.Y))
{
_scrollData._offset.Y = scrollY;
InvalidateMeasure();
}
}
///
/// StackPanel implementation of .
///
// The goal is to change offsets to bring the child into view, and return a rectangle in our space to make visible.
// The rectangle we return is in the physical dimension the input target rect transformed into our pace.
// In the logical dimension, it is our immediate child's rect.
// Note: This code presently assumes we/children are layout clean. See work item 22269 for more detail.
public Rect MakeVisible(Visual visual, Rect rectangle)
{
Vector newOffset = new Vector();
Rect newRect = new Rect();
// We can only work on visuals that are us or children.
// An empty rect has no size or position. We can't meaningfully use it.
if ( rectangle.IsEmpty
|| visual == null
|| visual == (Visual)this
|| !this.IsAncestorOf(visual))
{
return Rect.Empty;
}
#pragma warning disable 1634, 1691
#pragma warning disable 56506
// Compute the child's rect relative to (0,0) in our coordinate space.
// This is a false positive by PreSharp. visual cannot be null because of the 'if' check above
GeneralTransform childTransform = visual.TransformToAncestor(this);
#pragma warning restore 56506
#pragma warning restore 1634, 1691
rectangle = childTransform.TransformBounds(rectangle);
// We can't do any work unless we're scrolling.
if (!IsScrolling)
{
return rectangle;
}
// Bring the target rect into view in the physical dimension.
MakeVisiblePhysicalHelper(rectangle, ref newOffset, ref newRect);
// Bring our child containing the visual into view.
int childIndex = FindChildIndexThatParentsVisual(visual);
MakeVisibleLogicalHelper(childIndex, ref newOffset, ref newRect);
// We have computed the scrolling offsets; validate and scroll to them.
newOffset.X = ScrollContentPresenter.CoerceOffset(newOffset.X, _scrollData._extent.Width, _scrollData._viewport.Width);
newOffset.Y = ScrollContentPresenter.CoerceOffset(newOffset.Y, _scrollData._extent.Height, _scrollData._viewport.Height);
if (!DoubleUtil.AreClose(newOffset, _scrollData._offset))
{
_scrollData._offset = newOffset;
InvalidateMeasure();
OnScrollChange();
}
// Return the rectangle
return newRect;
}
#endregion
#endregion
//-------------------------------------------------------------------
//
// Public Properties
//
//--------------------------------------------------------------------
#region Public Properties
///
/// Specifies dimension of children stacking.
///
public Orientation Orientation
{
get { return (Orientation) GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
///
/// DependencyProperty for property.
///
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(
"Orientation",
typeof(Orientation),
typeof(StackPanel),
new FrameworkPropertyMetadata(
Orientation.Vertical,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(OnOrientationChanged)),
new ValidateValueCallback(ScrollBar.IsValidOrientation));
///
/// This property is always true because this panel has vertical or horizontal orientation
///
protected internal override bool HasLogicalOrientation
{
get { return true; }
}
///
/// Orientation of the panel if its layout is in one dimension.
/// Otherwise HasLogicalOrientation is false and LogicalOrientation should be ignored
///
protected internal override Orientation LogicalOrientation
{
get { return this.Orientation; }
}
//-----------------------------------------------------------
// IScrollInfo Properties
//-----------------------------------------------------------
#region IScrollInfo Properties
///
/// StackPanel reacts to this property by changing it's child measurement algorithm.
/// If scrolling in a dimension, infinite space is allowed the child; otherwise, available size is preserved.
///
[DefaultValue(false)]
public bool CanHorizontallyScroll
{
get
{
if (_scrollData == null) { return false; }
return _scrollData._allowHorizontal;
}
set
{
EnsureScrollData();
if (_scrollData._allowHorizontal != value)
{
_scrollData._allowHorizontal = value;
InvalidateMeasure();
}
}
}
///
/// StackPanel reacts to this property by changing it's child measurement algorithm.
/// If scrolling in a dimension, infinite space is allowed the child; otherwise, available size is preserved.
///
[DefaultValue(false)]
public bool CanVerticallyScroll
{
get
{
if (_scrollData == null) { return false; }
return _scrollData._allowVertical;
}
set
{
EnsureScrollData();
if (_scrollData._allowVertical != value)
{
_scrollData._allowVertical = value;
InvalidateMeasure();
}
}
}
///
/// ExtentWidth contains the horizontal size of the scrolled content element in 1/96"
///
public double ExtentWidth
{
get
{
if (_scrollData == null) { return 0.0; }
return _scrollData._extent.Width;
}
}
///
/// ExtentHeight contains the vertical size of the scrolled content element in 1/96"
///
public double ExtentHeight
{
get
{
if (_scrollData == null) { return 0.0; }
return _scrollData._extent.Height;
}
}
///
/// ViewportWidth contains the horizontal size of content's visible range in 1/96"
///
public double ViewportWidth
{
get
{
if (_scrollData == null) { return 0.0; }
return _scrollData._viewport.Width;
}
}
///
/// ViewportHeight contains the vertical size of content's visible range in 1/96"
///
public double ViewportHeight
{
get
{
if (_scrollData == null) { return 0.0; }
return _scrollData._viewport.Height;
}
}
///
/// HorizontalOffset is the horizontal offset of the scrolled content in 1/96".
///
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public double HorizontalOffset
{
get
{
if (_scrollData == null) { return 0.0; }
return _scrollData._computedOffset.X;
}
}
///
/// VerticalOffset is the vertical offset of the scrolled content in 1/96".
///
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public double VerticalOffset
{
get
{
if (_scrollData == null) { return 0.0; }
return _scrollData._computedOffset.Y;
}
}
///
/// ScrollOwner is the container that controls any scrollbars, headers, etc... that are dependant
/// on this IScrollInfo's properties.
///
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ScrollViewer ScrollOwner
{
get
{
EnsureScrollData();
return _scrollData._scrollOwner;
}
set
{
EnsureScrollData();
if (value != _scrollData._scrollOwner)
{
ResetScrolling(this);
_scrollData._scrollOwner = value;
}
}
}
#endregion IScrollInfo Properties
#endregion Public Properties
//-------------------------------------------------------------------
//
// Protected Methods
//
//--------------------------------------------------------------------
#region Protected Methods
///
/// General StackPanel layout behavior is to grow unbounded in the "stacking" direction (Size To Content).
/// Children in this dimension are encouraged to be as large as they like. In the other dimension,
/// StackPanel will assume the maximum size of its children.
///
///
/// When scrolling, StackPanel will not grow in layout size but effectively add the children on a z-plane which
/// will probably be clipped by some parent (typically a ScrollContentPresenter) to Stack's size.
///
/// Constraint
/// Desired size
protected override Size MeasureOverride(Size constraint)
{
#if Profiling
if (Panel.IsAboutToGenerateContent(this))
return MeasureOverrideProfileStub(constraint);
else
return RealMeasureOverride(constraint);
}
// this is a handy place to start/stop profiling
private Size MeasureOverrideProfileStub(Size constraint)
{
return RealMeasureOverride(constraint);
}
private Size RealMeasureOverride(Size constraint)
{
#endif
UIElementCollection children = InternalChildren;
Size stackDesiredSize = new Size();
Size layoutSlotSize = constraint;
bool fHorizontal = (Orientation == Orientation.Horizontal);
int firstViewport; // First child index in the viewport.
int lastViewport = -1; // Last child index in the viewport. -1 indicates we have not yet iterated through the last child.
double logicalVisibleSpace, childLogicalSize;
bool etwTracingEnabled = IsScrolling && EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal);
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.GENERICSTRINGGUID), MS.Utility.EventType.StartEvent, "STACK:MeasureOverride");
}
//
// Initialize child sizing and iterator data
// Allow children as much size as they want along the stack.
//
if (fHorizontal)
{
layoutSlotSize.Width = Double.PositiveInfinity;
if (IsScrolling && CanVerticallyScroll) { layoutSlotSize.Height = Double.PositiveInfinity; }
firstViewport = (IsScrolling) ? CoerceOffsetToInteger(_scrollData._offset.X, children.Count) : 0;
logicalVisibleSpace = constraint.Width;
}
else
{
layoutSlotSize.Height = Double.PositiveInfinity;
if (IsScrolling && CanHorizontallyScroll) { layoutSlotSize.Width = Double.PositiveInfinity; }
firstViewport = (IsScrolling) ? CoerceOffsetToInteger(_scrollData._offset.Y, children.Count) : 0;
logicalVisibleSpace = constraint.Height;
}
//
// Iterate through children.
// While we still supported virtualization, this was hidden in a child iterator (see source history).
//
for (int i = 0, count = children.Count; i < count; ++i)
{
// Get next child.
UIElement child = children[i];
if (child == null) { continue; }
// Measure the child.
child.Measure(layoutSlotSize);
Size childDesiredSize = child.DesiredSize;
// Accumulate child size.
if (fHorizontal)
{
stackDesiredSize.Width += childDesiredSize.Width;
stackDesiredSize.Height = Math.Max(stackDesiredSize.Height, childDesiredSize.Height);
childLogicalSize = childDesiredSize.Width;
}
else
{
stackDesiredSize.Width = Math.Max(stackDesiredSize.Width, childDesiredSize.Width);
stackDesiredSize.Height += childDesiredSize.Height;
childLogicalSize = childDesiredSize.Height;
}
// Adjust remaining viewport space if we are scrolling and within the viewport region.
// While scrolling (not virtualizing), we always measure children before and after the viewport.
if (IsScrolling && lastViewport == -1 && i >= firstViewport)
{
logicalVisibleSpace -= childLogicalSize;
if (DoubleUtil.LessThanOrClose(logicalVisibleSpace, 0.0))
{
lastViewport = i;
}
}
}
//
// Compute Scrolling stuff.
//
if (IsScrolling)
{
// Compute viewport and extent.
Size viewport = constraint;
Size extent = stackDesiredSize;
Vector offset = _scrollData._offset;
// If we have not yet set the last child in the viewport, set it to the last child.
if (lastViewport == -1) { lastViewport = children.Count - 1; }
// If we or children have resized, it's possible that we can now display more content.
// This is true if we started at a nonzero offeset and still have space remaining.
// In this case, we loop back through previous children until we run out of space.
while (firstViewport > 0)
{
double projectedLogicalVisibleSpace = logicalVisibleSpace;
if (fHorizontal) { projectedLogicalVisibleSpace -= children[firstViewport - 1].DesiredSize.Width; }
else { projectedLogicalVisibleSpace -= children[firstViewport - 1].DesiredSize.Height; }
// If we have run out of room, break.
if (DoubleUtil.LessThan(projectedLogicalVisibleSpace, 0.0)) { break; }
// Adjust viewport
firstViewport--;
logicalVisibleSpace = projectedLogicalVisibleSpace;
}
int logicalExtent = children.Count;
int logicalViewport = lastViewport - firstViewport;
// We are conservative when estimating a viewport, not including the last element in case it is only partially visible.
// We want to count it if it is fully visible (>= 0 space remaining) or the only element in the viewport.
if (logicalViewport == 0 || DoubleUtil.GreaterThanOrClose(logicalVisibleSpace, 0.0)) { logicalViewport++; }
if (fHorizontal)
{
_scrollData._physicalViewport = viewport.Width;
viewport.Width = logicalViewport;
extent.Width = logicalExtent;
offset.X = firstViewport;
offset.Y = Math.Max(0, Math.Min(offset.Y, extent.Height - viewport.Height));
}
else
{
_scrollData._physicalViewport = viewport.Height;
viewport.Height = logicalViewport;
extent.Height = logicalExtent;
offset.Y = firstViewport;
offset.X = Math.Max(0, Math.Min(offset.X, extent.Width - viewport.Width));
}
// Since we can offset and clip our content, we never need to be larger than the parent suggestion.
// If we returned the full size of the content, we would always be so big we didn't need to scroll. :)
stackDesiredSize.Width = Math.Min(stackDesiredSize.Width, constraint.Width);
stackDesiredSize.Height = Math.Min(stackDesiredSize.Height, constraint.Height);
// Verify Scroll Info, invalidate ScrollOwner if necessary.
VerifyScrollingData(viewport, extent, offset);
}
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.GENERICSTRINGGUID), MS.Utility.EventType.EndEvent, "STACK:MeasureOverride");
}
return stackDesiredSize;
}
///
/// Content arrangement.
///
/// Arrange size
protected override Size ArrangeOverride(Size arrangeSize)
{
UIElementCollection children = this.Children;
bool fHorizontal = (Orientation == Orientation.Horizontal);
Rect rcChild = new Rect(arrangeSize);
double previousChildSize = 0.0;
bool etwTracingEnabled = IsScrolling && EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal);
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.GENERICSTRINGGUID), MS.Utility.EventType.StartEvent, "STACK:ArrangeOverride");
}
//
// Compute scroll offset and seed it into rcChild.
//
if (IsScrolling)
{
if (fHorizontal)
{
rcChild.X = ComputePhysicalFromLogicalOffset(_scrollData._computedOffset.X, true);
rcChild.Y = -1.0 * _scrollData._computedOffset.Y;
}
else
{
rcChild.X = -1.0 * _scrollData._computedOffset.X;
rcChild.Y = ComputePhysicalFromLogicalOffset(_scrollData._computedOffset.Y, false);
}
}
//
// Arrange and Position Children.
//
for (int i = 0, count = children.Count; i < count; ++i)
{
UIElement child = (UIElement)children[i];
if (child == null) { continue; }
if (fHorizontal)
{
rcChild.X += previousChildSize;
previousChildSize = child.DesiredSize.Width;
rcChild.Width = previousChildSize;
rcChild.Height = Math.Max(arrangeSize.Height, child.DesiredSize.Height);
}
else
{
rcChild.Y += previousChildSize;
previousChildSize = child.DesiredSize.Height;
rcChild.Height = previousChildSize;
rcChild.Width = Math.Max(arrangeSize.Width, child.DesiredSize.Width);
}
child.Arrange(rcChild);
}
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.GENERICSTRINGGUID), MS.Utility.EventType.EndEvent, "STACK:ArrangeOverride");
}
return arrangeSize;
}
#endregion Protected Methods
//-----------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
private void EnsureScrollData()
{
if (_scrollData == null) { _scrollData = new ScrollData(); }
}
private static void ResetScrolling(StackPanel element)
{
element.InvalidateMeasure();
// Clear scrolling data. Because of thrash (being disconnected & reconnected, &c...), we may
if (element.IsScrolling)
{
element._scrollData.ClearLayout();
}
}
// OnScrollChange is an override called whenever the IScrollInfo exposed scrolling state changes on this element.
// At the time this method is called, scrolling state is in its new, valid state.
private void OnScrollChange()
{
if (ScrollOwner != null) { ScrollOwner.InvalidateScrollInfo(); }
}
private void VerifyScrollingData(Size viewport, Size extent, Vector offset)
{
bool fValid = true;
Debug.Assert(IsScrolling);
fValid &= DoubleUtil.AreClose(viewport, _scrollData._viewport);
fValid &= DoubleUtil.AreClose(extent, _scrollData._extent);
fValid &= DoubleUtil.AreClose(offset, _scrollData._computedOffset);
_scrollData._offset = offset;
if (!fValid)
{
_scrollData._viewport = viewport;
_scrollData._extent = extent;
_scrollData._computedOffset = offset;
OnScrollChange();
}
}
// Translates a logical (child index) offset to a physical (1/96") when scrolling.
// If virtualizing, it makes the assumption that the logicalOffset is always the first in the visual collection
// and thus returns 0.
// If not virtualizing, it assumes that children are Measure clean; should only be called after running Measure.
private double ComputePhysicalFromLogicalOffset(double logicalOffset, bool fHorizontal)
{
double physicalOffset = 0.0;
UIElementCollection children = this.Children;
Debug.Assert(logicalOffset == 0 || (logicalOffset > 0 && logicalOffset < children.Count));
for (int i = 0; i < logicalOffset; i++)
{
physicalOffset -= (fHorizontal)
? ((UIElement)children[i]).DesiredSize.Width
: ((UIElement)children[i]).DesiredSize.Height;
}
return physicalOffset;
}
private int FindChildIndexThatParentsVisual(Visual child)
{
DependencyObject dependencyObjectChild = child;
DependencyObject parent = VisualTreeHelper.GetParent(child);
while (parent != this)
{
dependencyObjectChild = parent;
parent = VisualTreeHelper.GetParent(dependencyObjectChild);
if (parent == null)
{
throw new ArgumentException(SR.Get(SRID.Stack_VisualInDifferentSubTree),"child");
}
}
UIElementCollection children = this.Children;
//The Downcast is ok because StackPanel's
//child has to be a UIElement to be in this.Children collection
return (children.IndexOf((UIElement)dependencyObjectChild));
}
private void MakeVisiblePhysicalHelper(Rect r, ref Vector newOffset, ref Rect newRect)
{
double viewportOffset;
double viewportSize;
double targetRectOffset;
double targetRectSize;
double minPhysicalOffset;
bool fHorizontal = (Orientation == Orientation.Horizontal);
if (fHorizontal)
{
viewportOffset = _scrollData._computedOffset.Y;
viewportSize = ViewportHeight;
targetRectOffset = r.Y;
targetRectSize = r.Height;
}
else
{
viewportOffset = _scrollData._computedOffset.X;
viewportSize = ViewportWidth;
targetRectOffset = r.X;
targetRectSize = r.Width;
}
targetRectOffset += viewportOffset;
minPhysicalOffset = ScrollContentPresenter.ComputeScrollOffsetWithMinimalScroll(
viewportOffset, viewportOffset + viewportSize, targetRectOffset, targetRectOffset + targetRectSize);
// Compute the visible rectangle of the child relative to the viewport.
double left = Math.Max(targetRectOffset, minPhysicalOffset);
targetRectSize = Math.Max(Math.Min(targetRectSize + targetRectOffset, minPhysicalOffset + viewportSize) - left, 0);
targetRectOffset = left;
targetRectOffset -= viewportOffset;
if (fHorizontal)
{
newOffset.Y = minPhysicalOffset;
newRect.Y = targetRectOffset;
newRect.Height = targetRectSize;
}
else
{
newOffset.X = minPhysicalOffset;
newRect.X = targetRectOffset;
newRect.Width = targetRectSize;
}
}
private void MakeVisibleLogicalHelper(int childIndex, ref Vector newOffset, ref Rect newRect)
{
bool fHorizontal = (Orientation == Orientation.Horizontal);
int firstChildInView;
int newFirstChild;
int viewportSize;
double childOffsetWithinViewport = 0;
if (fHorizontal)
{
firstChildInView = (int)_scrollData._computedOffset.X;
viewportSize = (int)_scrollData._viewport.Width;
}
else
{
firstChildInView = (int)_scrollData._computedOffset.Y;
viewportSize = (int)_scrollData._viewport.Height;
}
newFirstChild = firstChildInView;
// If the target child is before the current viewport, move the viewport to put the child at the top.
if (childIndex < firstChildInView)
{
newFirstChild = childIndex;
}
// If the target child is after the current viewport, move the viewport to put the child at the bottom.
else if (childIndex > firstChildInView + viewportSize - 1)
{
Size childDesiredSize = InternalChildren[childIndex].DesiredSize;
double nextChildSize = ((fHorizontal) ? childDesiredSize.Width : childDesiredSize.Height);
double viewportSpace = _scrollData._physicalViewport - nextChildSize;
int i = childIndex;
while (i > 0 && DoubleUtil.GreaterThanOrClose(viewportSpace, 0.0))
{
i--;
childDesiredSize = InternalChildren[i].DesiredSize;
nextChildSize = ((fHorizontal) ? childDesiredSize.Width : childDesiredSize.Height);
childOffsetWithinViewport += nextChildSize;
viewportSpace -= nextChildSize;
}
if (i != childIndex && DoubleUtil.LessThan(viewportSpace, 0.0))
{
childOffsetWithinViewport -= nextChildSize;
i++;
}
newFirstChild = i;
}
if (fHorizontal)
{
newOffset.X = newFirstChild;
newRect.X = childOffsetWithinViewport;
newRect.Width = InternalChildren[childIndex].DesiredSize.Width;
}
else
{
newOffset.Y = newFirstChild;
newRect.Y = childOffsetWithinViewport;
newRect.Height = InternalChildren[childIndex].DesiredSize.Height;
}
}
static private int CoerceOffsetToInteger(double offset, int numberOfItems)
{
int iNewOffset;
if (Double.IsNegativeInfinity(offset))
{
iNewOffset = 0;
}
else if (Double.IsPositiveInfinity(offset))
{
iNewOffset = numberOfItems - 1;
}
else
{
iNewOffset = (int)offset;
iNewOffset = Math.Max(Math.Min(numberOfItems - 1, iNewOffset), 0);
}
return iNewOffset;
}
//------------------------------------------------------------
// Avalon Property Callbacks/Overrides
//-----------------------------------------------------------
#region Avalon Property Callbacks/Overrides
///
///
///
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Since Orientation is so essential to logical scrolling/virtualization, we synchronously check if
// the new value is different and clear all scrolling data if so.
ResetScrolling(d as StackPanel);
}
#endregion
#endregion Private Methods
//------------------------------------------------------
//
// Private Properties
//
//-----------------------------------------------------
#region Private Properties
private bool IsScrolling
{
get { return (_scrollData != null) && (_scrollData._scrollOwner != null); }
}
//
// This property
// 1. Finds the correct initial size for the _effectiveValues store on the current DependencyObject
// 2. This is a performance optimization
//
internal override int EffectiveValuesInitialSize
{
get { return 9; }
}
#endregion Private Properties
//-----------------------------------------------------
//
// Private Fields
//
//-----------------------------------------------------
#region Private Fields
// Logical scrolling and virtualization data.
private ScrollData _scrollData;
#endregion Private Fields
//------------------------------------------------------
//
// Private Structures / Classes
//
//-----------------------------------------------------
#region Private Structures Classes
//------------------------------------------------------------
// ScrollData class
//------------------------------------------------------------
#region ScrollData
// Helper class to hold scrolling data.
// This class exists to reduce working set when StackPanel is used outside a scrolling situation.
// Standard "extra pointer always for less data sometimes" cache savings model:
// !Scroll [1xReference]
// Scroll [1xReference] + [6xDouble + 1xReference]
private class ScrollData
{
// Clears layout generated data.
// Does not clear scrollOwner, because unless resetting due to a scrollOwner change, we won't get reattached.
internal void ClearLayout()
{
_offset = new Vector();
_viewport = _extent = new Size();
_physicalViewport = 0;
}
// For Stack/Flow, the two dimensions of properties are in different units:
// 1. The "logically scrolling" dimension uses items as units.
// 2. The other dimension physically scrolls. Units are in Avalon pixels (1/96").
internal bool _allowHorizontal;
internal bool _allowVertical;
internal Vector _offset; // Scroll offset of content. Positive corresponds to a visually upward offset.
internal Vector _computedOffset = new Vector(0,0);
internal Size _viewport; // ViewportSize is in {pixels x items} (or vice-versa).
internal Size _extent; // Extent is the total number of children (logical dimension) or physical size
internal double _physicalViewport; // The physical size of the viewport for the items dimension above.
internal ScrollViewer _scrollOwner; // ScrollViewer to which we're attached.
}
#endregion ScrollData
#endregion Private Structures Classes
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
// File: Stack.cs
//
// Description: Implementation of StackPanel class.
// Spec at http://avalon/layout/Specs/StackPanel.doc
//
// History:
// 06/25/2004 : olego - Created
// 09/03/2004 : greglett - Converted to new layout system, IScrollInfo implementation
//
//---------------------------------------------------------------------------
//#define Profiling
using MS.Internal;
using MS.Utility;
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Threading;
namespace System.Windows.Controls
{
///
/// StackPanel is used to arrange children into single line.
///
public class StackPanel : Panel, IScrollInfo
{
//-------------------------------------------------------------------
//
// Constructors
//
//-------------------------------------------------------------------
#region Constructors
///
/// Default constructor.
///
public StackPanel() : base()
{
}
#endregion Constructors
//--------------------------------------------------------------------
//
// Public Methods
//
//-------------------------------------------------------------------
#region Public Methods
//------------------------------------------------------------
// IScrollInfo Methods
//------------------------------------------------------------
#region IScrollInfo Methods
///
/// Scroll content by one line to the top.
///
public void LineUp()
{
SetVerticalOffset(VerticalOffset - ((Orientation == Orientation.Vertical) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one line to the bottom.
///
public void LineDown()
{
SetVerticalOffset(VerticalOffset + ((Orientation == Orientation.Vertical) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one line to the left.
///
public void LineLeft()
{
SetHorizontalOffset(HorizontalOffset - ((Orientation == Orientation.Horizontal) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one line to the right.
///
public void LineRight()
{
SetHorizontalOffset(HorizontalOffset + ((Orientation == Orientation.Horizontal) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one page to the top.
///
public void PageUp()
{
SetVerticalOffset(VerticalOffset - ViewportHeight);
}
///
/// Scroll content by one page to the bottom.
///
public void PageDown()
{
SetVerticalOffset(VerticalOffset + ViewportHeight);
}
///
/// Scroll content by one page to the left.
///
public void PageLeft()
{
SetHorizontalOffset(HorizontalOffset - ViewportWidth);
}
///
/// Scroll content by one page to the right.
///
public void PageRight()
{
SetHorizontalOffset(HorizontalOffset + ViewportWidth);
}
///
/// Scroll content by one page to the top.
///
public void MouseWheelUp()
{
SetVerticalOffset(VerticalOffset - SystemParameters.WheelScrollLines * ((Orientation == Orientation.Vertical) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one page to the bottom.
///
public void MouseWheelDown()
{
SetVerticalOffset(VerticalOffset + SystemParameters.WheelScrollLines * ((Orientation == Orientation.Vertical) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one page to the left.
///
public void MouseWheelLeft()
{
SetHorizontalOffset(HorizontalOffset - 3.0 * ((Orientation == Orientation.Horizontal) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Scroll content by one page to the right.
///
public void MouseWheelRight()
{
SetHorizontalOffset(HorizontalOffset + 3.0 * ((Orientation == Orientation.Horizontal) ? 1.0 : ScrollViewer._scrollLineDelta));
}
///
/// Set the HorizontalOffset to the passed value.
///
public void SetHorizontalOffset(double offset)
{
EnsureScrollData();
double scrollX = ScrollContentPresenter.ValidateInputOffset(offset, "HorizontalOffset");
if (!DoubleUtil.AreClose(scrollX, _scrollData._offset.X))
{
_scrollData._offset.X = scrollX;
InvalidateMeasure();
}
}
///
/// Set the VerticalOffset to the passed value.
///
public void SetVerticalOffset(double offset)
{
EnsureScrollData();
double scrollY = ScrollContentPresenter.ValidateInputOffset(offset, "VerticalOffset");
if (!DoubleUtil.AreClose(scrollY, _scrollData._offset.Y))
{
_scrollData._offset.Y = scrollY;
InvalidateMeasure();
}
}
///
/// StackPanel implementation of .
///
// The goal is to change offsets to bring the child into view, and return a rectangle in our space to make visible.
// The rectangle we return is in the physical dimension the input target rect transformed into our pace.
// In the logical dimension, it is our immediate child's rect.
// Note: This code presently assumes we/children are layout clean. See work item 22269 for more detail.
public Rect MakeVisible(Visual visual, Rect rectangle)
{
Vector newOffset = new Vector();
Rect newRect = new Rect();
// We can only work on visuals that are us or children.
// An empty rect has no size or position. We can't meaningfully use it.
if ( rectangle.IsEmpty
|| visual == null
|| visual == (Visual)this
|| !this.IsAncestorOf(visual))
{
return Rect.Empty;
}
#pragma warning disable 1634, 1691
#pragma warning disable 56506
// Compute the child's rect relative to (0,0) in our coordinate space.
// This is a false positive by PreSharp. visual cannot be null because of the 'if' check above
GeneralTransform childTransform = visual.TransformToAncestor(this);
#pragma warning restore 56506
#pragma warning restore 1634, 1691
rectangle = childTransform.TransformBounds(rectangle);
// We can't do any work unless we're scrolling.
if (!IsScrolling)
{
return rectangle;
}
// Bring the target rect into view in the physical dimension.
MakeVisiblePhysicalHelper(rectangle, ref newOffset, ref newRect);
// Bring our child containing the visual into view.
int childIndex = FindChildIndexThatParentsVisual(visual);
MakeVisibleLogicalHelper(childIndex, ref newOffset, ref newRect);
// We have computed the scrolling offsets; validate and scroll to them.
newOffset.X = ScrollContentPresenter.CoerceOffset(newOffset.X, _scrollData._extent.Width, _scrollData._viewport.Width);
newOffset.Y = ScrollContentPresenter.CoerceOffset(newOffset.Y, _scrollData._extent.Height, _scrollData._viewport.Height);
if (!DoubleUtil.AreClose(newOffset, _scrollData._offset))
{
_scrollData._offset = newOffset;
InvalidateMeasure();
OnScrollChange();
}
// Return the rectangle
return newRect;
}
#endregion
#endregion
//-------------------------------------------------------------------
//
// Public Properties
//
//--------------------------------------------------------------------
#region Public Properties
///
/// Specifies dimension of children stacking.
///
public Orientation Orientation
{
get { return (Orientation) GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
///
/// DependencyProperty for property.
///
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(
"Orientation",
typeof(Orientation),
typeof(StackPanel),
new FrameworkPropertyMetadata(
Orientation.Vertical,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(OnOrientationChanged)),
new ValidateValueCallback(ScrollBar.IsValidOrientation));
///
/// This property is always true because this panel has vertical or horizontal orientation
///
protected internal override bool HasLogicalOrientation
{
get { return true; }
}
///
/// Orientation of the panel if its layout is in one dimension.
/// Otherwise HasLogicalOrientation is false and LogicalOrientation should be ignored
///
protected internal override Orientation LogicalOrientation
{
get { return this.Orientation; }
}
//-----------------------------------------------------------
// IScrollInfo Properties
//-----------------------------------------------------------
#region IScrollInfo Properties
///
/// StackPanel reacts to this property by changing it's child measurement algorithm.
/// If scrolling in a dimension, infinite space is allowed the child; otherwise, available size is preserved.
///
[DefaultValue(false)]
public bool CanHorizontallyScroll
{
get
{
if (_scrollData == null) { return false; }
return _scrollData._allowHorizontal;
}
set
{
EnsureScrollData();
if (_scrollData._allowHorizontal != value)
{
_scrollData._allowHorizontal = value;
InvalidateMeasure();
}
}
}
///
/// StackPanel reacts to this property by changing it's child measurement algorithm.
/// If scrolling in a dimension, infinite space is allowed the child; otherwise, available size is preserved.
///
[DefaultValue(false)]
public bool CanVerticallyScroll
{
get
{
if (_scrollData == null) { return false; }
return _scrollData._allowVertical;
}
set
{
EnsureScrollData();
if (_scrollData._allowVertical != value)
{
_scrollData._allowVertical = value;
InvalidateMeasure();
}
}
}
///
/// ExtentWidth contains the horizontal size of the scrolled content element in 1/96"
///
public double ExtentWidth
{
get
{
if (_scrollData == null) { return 0.0; }
return _scrollData._extent.Width;
}
}
///
/// ExtentHeight contains the vertical size of the scrolled content element in 1/96"
///
public double ExtentHeight
{
get
{
if (_scrollData == null) { return 0.0; }
return _scrollData._extent.Height;
}
}
///
/// ViewportWidth contains the horizontal size of content's visible range in 1/96"
///
public double ViewportWidth
{
get
{
if (_scrollData == null) { return 0.0; }
return _scrollData._viewport.Width;
}
}
///
/// ViewportHeight contains the vertical size of content's visible range in 1/96"
///
public double ViewportHeight
{
get
{
if (_scrollData == null) { return 0.0; }
return _scrollData._viewport.Height;
}
}
///
/// HorizontalOffset is the horizontal offset of the scrolled content in 1/96".
///
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public double HorizontalOffset
{
get
{
if (_scrollData == null) { return 0.0; }
return _scrollData._computedOffset.X;
}
}
///
/// VerticalOffset is the vertical offset of the scrolled content in 1/96".
///
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public double VerticalOffset
{
get
{
if (_scrollData == null) { return 0.0; }
return _scrollData._computedOffset.Y;
}
}
///
/// ScrollOwner is the container that controls any scrollbars, headers, etc... that are dependant
/// on this IScrollInfo's properties.
///
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ScrollViewer ScrollOwner
{
get
{
EnsureScrollData();
return _scrollData._scrollOwner;
}
set
{
EnsureScrollData();
if (value != _scrollData._scrollOwner)
{
ResetScrolling(this);
_scrollData._scrollOwner = value;
}
}
}
#endregion IScrollInfo Properties
#endregion Public Properties
//-------------------------------------------------------------------
//
// Protected Methods
//
//--------------------------------------------------------------------
#region Protected Methods
///
/// General StackPanel layout behavior is to grow unbounded in the "stacking" direction (Size To Content).
/// Children in this dimension are encouraged to be as large as they like. In the other dimension,
/// StackPanel will assume the maximum size of its children.
///
///
/// When scrolling, StackPanel will not grow in layout size but effectively add the children on a z-plane which
/// will probably be clipped by some parent (typically a ScrollContentPresenter) to Stack's size.
///
/// Constraint
/// Desired size
protected override Size MeasureOverride(Size constraint)
{
#if Profiling
if (Panel.IsAboutToGenerateContent(this))
return MeasureOverrideProfileStub(constraint);
else
return RealMeasureOverride(constraint);
}
// this is a handy place to start/stop profiling
private Size MeasureOverrideProfileStub(Size constraint)
{
return RealMeasureOverride(constraint);
}
private Size RealMeasureOverride(Size constraint)
{
#endif
UIElementCollection children = InternalChildren;
Size stackDesiredSize = new Size();
Size layoutSlotSize = constraint;
bool fHorizontal = (Orientation == Orientation.Horizontal);
int firstViewport; // First child index in the viewport.
int lastViewport = -1; // Last child index in the viewport. -1 indicates we have not yet iterated through the last child.
double logicalVisibleSpace, childLogicalSize;
bool etwTracingEnabled = IsScrolling && EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal);
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.GENERICSTRINGGUID), MS.Utility.EventType.StartEvent, "STACK:MeasureOverride");
}
//
// Initialize child sizing and iterator data
// Allow children as much size as they want along the stack.
//
if (fHorizontal)
{
layoutSlotSize.Width = Double.PositiveInfinity;
if (IsScrolling && CanVerticallyScroll) { layoutSlotSize.Height = Double.PositiveInfinity; }
firstViewport = (IsScrolling) ? CoerceOffsetToInteger(_scrollData._offset.X, children.Count) : 0;
logicalVisibleSpace = constraint.Width;
}
else
{
layoutSlotSize.Height = Double.PositiveInfinity;
if (IsScrolling && CanHorizontallyScroll) { layoutSlotSize.Width = Double.PositiveInfinity; }
firstViewport = (IsScrolling) ? CoerceOffsetToInteger(_scrollData._offset.Y, children.Count) : 0;
logicalVisibleSpace = constraint.Height;
}
//
// Iterate through children.
// While we still supported virtualization, this was hidden in a child iterator (see source history).
//
for (int i = 0, count = children.Count; i < count; ++i)
{
// Get next child.
UIElement child = children[i];
if (child == null) { continue; }
// Measure the child.
child.Measure(layoutSlotSize);
Size childDesiredSize = child.DesiredSize;
// Accumulate child size.
if (fHorizontal)
{
stackDesiredSize.Width += childDesiredSize.Width;
stackDesiredSize.Height = Math.Max(stackDesiredSize.Height, childDesiredSize.Height);
childLogicalSize = childDesiredSize.Width;
}
else
{
stackDesiredSize.Width = Math.Max(stackDesiredSize.Width, childDesiredSize.Width);
stackDesiredSize.Height += childDesiredSize.Height;
childLogicalSize = childDesiredSize.Height;
}
// Adjust remaining viewport space if we are scrolling and within the viewport region.
// While scrolling (not virtualizing), we always measure children before and after the viewport.
if (IsScrolling && lastViewport == -1 && i >= firstViewport)
{
logicalVisibleSpace -= childLogicalSize;
if (DoubleUtil.LessThanOrClose(logicalVisibleSpace, 0.0))
{
lastViewport = i;
}
}
}
//
// Compute Scrolling stuff.
//
if (IsScrolling)
{
// Compute viewport and extent.
Size viewport = constraint;
Size extent = stackDesiredSize;
Vector offset = _scrollData._offset;
// If we have not yet set the last child in the viewport, set it to the last child.
if (lastViewport == -1) { lastViewport = children.Count - 1; }
// If we or children have resized, it's possible that we can now display more content.
// This is true if we started at a nonzero offeset and still have space remaining.
// In this case, we loop back through previous children until we run out of space.
while (firstViewport > 0)
{
double projectedLogicalVisibleSpace = logicalVisibleSpace;
if (fHorizontal) { projectedLogicalVisibleSpace -= children[firstViewport - 1].DesiredSize.Width; }
else { projectedLogicalVisibleSpace -= children[firstViewport - 1].DesiredSize.Height; }
// If we have run out of room, break.
if (DoubleUtil.LessThan(projectedLogicalVisibleSpace, 0.0)) { break; }
// Adjust viewport
firstViewport--;
logicalVisibleSpace = projectedLogicalVisibleSpace;
}
int logicalExtent = children.Count;
int logicalViewport = lastViewport - firstViewport;
// We are conservative when estimating a viewport, not including the last element in case it is only partially visible.
// We want to count it if it is fully visible (>= 0 space remaining) or the only element in the viewport.
if (logicalViewport == 0 || DoubleUtil.GreaterThanOrClose(logicalVisibleSpace, 0.0)) { logicalViewport++; }
if (fHorizontal)
{
_scrollData._physicalViewport = viewport.Width;
viewport.Width = logicalViewport;
extent.Width = logicalExtent;
offset.X = firstViewport;
offset.Y = Math.Max(0, Math.Min(offset.Y, extent.Height - viewport.Height));
}
else
{
_scrollData._physicalViewport = viewport.Height;
viewport.Height = logicalViewport;
extent.Height = logicalExtent;
offset.Y = firstViewport;
offset.X = Math.Max(0, Math.Min(offset.X, extent.Width - viewport.Width));
}
// Since we can offset and clip our content, we never need to be larger than the parent suggestion.
// If we returned the full size of the content, we would always be so big we didn't need to scroll. :)
stackDesiredSize.Width = Math.Min(stackDesiredSize.Width, constraint.Width);
stackDesiredSize.Height = Math.Min(stackDesiredSize.Height, constraint.Height);
// Verify Scroll Info, invalidate ScrollOwner if necessary.
VerifyScrollingData(viewport, extent, offset);
}
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.GENERICSTRINGGUID), MS.Utility.EventType.EndEvent, "STACK:MeasureOverride");
}
return stackDesiredSize;
}
///
/// Content arrangement.
///
/// Arrange size
protected override Size ArrangeOverride(Size arrangeSize)
{
UIElementCollection children = this.Children;
bool fHorizontal = (Orientation == Orientation.Horizontal);
Rect rcChild = new Rect(arrangeSize);
double previousChildSize = 0.0;
bool etwTracingEnabled = IsScrolling && EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal);
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.GENERICSTRINGGUID), MS.Utility.EventType.StartEvent, "STACK:ArrangeOverride");
}
//
// Compute scroll offset and seed it into rcChild.
//
if (IsScrolling)
{
if (fHorizontal)
{
rcChild.X = ComputePhysicalFromLogicalOffset(_scrollData._computedOffset.X, true);
rcChild.Y = -1.0 * _scrollData._computedOffset.Y;
}
else
{
rcChild.X = -1.0 * _scrollData._computedOffset.X;
rcChild.Y = ComputePhysicalFromLogicalOffset(_scrollData._computedOffset.Y, false);
}
}
//
// Arrange and Position Children.
//
for (int i = 0, count = children.Count; i < count; ++i)
{
UIElement child = (UIElement)children[i];
if (child == null) { continue; }
if (fHorizontal)
{
rcChild.X += previousChildSize;
previousChildSize = child.DesiredSize.Width;
rcChild.Width = previousChildSize;
rcChild.Height = Math.Max(arrangeSize.Height, child.DesiredSize.Height);
}
else
{
rcChild.Y += previousChildSize;
previousChildSize = child.DesiredSize.Height;
rcChild.Height = previousChildSize;
rcChild.Width = Math.Max(arrangeSize.Width, child.DesiredSize.Width);
}
child.Arrange(rcChild);
}
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.GENERICSTRINGGUID), MS.Utility.EventType.EndEvent, "STACK:ArrangeOverride");
}
return arrangeSize;
}
#endregion Protected Methods
//-----------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
private void EnsureScrollData()
{
if (_scrollData == null) { _scrollData = new ScrollData(); }
}
private static void ResetScrolling(StackPanel element)
{
element.InvalidateMeasure();
// Clear scrolling data. Because of thrash (being disconnected & reconnected, &c...), we may
if (element.IsScrolling)
{
element._scrollData.ClearLayout();
}
}
// OnScrollChange is an override called whenever the IScrollInfo exposed scrolling state changes on this element.
// At the time this method is called, scrolling state is in its new, valid state.
private void OnScrollChange()
{
if (ScrollOwner != null) { ScrollOwner.InvalidateScrollInfo(); }
}
private void VerifyScrollingData(Size viewport, Size extent, Vector offset)
{
bool fValid = true;
Debug.Assert(IsScrolling);
fValid &= DoubleUtil.AreClose(viewport, _scrollData._viewport);
fValid &= DoubleUtil.AreClose(extent, _scrollData._extent);
fValid &= DoubleUtil.AreClose(offset, _scrollData._computedOffset);
_scrollData._offset = offset;
if (!fValid)
{
_scrollData._viewport = viewport;
_scrollData._extent = extent;
_scrollData._computedOffset = offset;
OnScrollChange();
}
}
// Translates a logical (child index) offset to a physical (1/96") when scrolling.
// If virtualizing, it makes the assumption that the logicalOffset is always the first in the visual collection
// and thus returns 0.
// If not virtualizing, it assumes that children are Measure clean; should only be called after running Measure.
private double ComputePhysicalFromLogicalOffset(double logicalOffset, bool fHorizontal)
{
double physicalOffset = 0.0;
UIElementCollection children = this.Children;
Debug.Assert(logicalOffset == 0 || (logicalOffset > 0 && logicalOffset < children.Count));
for (int i = 0; i < logicalOffset; i++)
{
physicalOffset -= (fHorizontal)
? ((UIElement)children[i]).DesiredSize.Width
: ((UIElement)children[i]).DesiredSize.Height;
}
return physicalOffset;
}
private int FindChildIndexThatParentsVisual(Visual child)
{
DependencyObject dependencyObjectChild = child;
DependencyObject parent = VisualTreeHelper.GetParent(child);
while (parent != this)
{
dependencyObjectChild = parent;
parent = VisualTreeHelper.GetParent(dependencyObjectChild);
if (parent == null)
{
throw new ArgumentException(SR.Get(SRID.Stack_VisualInDifferentSubTree),"child");
}
}
UIElementCollection children = this.Children;
//The Downcast is ok because StackPanel's
//child has to be a UIElement to be in this.Children collection
return (children.IndexOf((UIElement)dependencyObjectChild));
}
private void MakeVisiblePhysicalHelper(Rect r, ref Vector newOffset, ref Rect newRect)
{
double viewportOffset;
double viewportSize;
double targetRectOffset;
double targetRectSize;
double minPhysicalOffset;
bool fHorizontal = (Orientation == Orientation.Horizontal);
if (fHorizontal)
{
viewportOffset = _scrollData._computedOffset.Y;
viewportSize = ViewportHeight;
targetRectOffset = r.Y;
targetRectSize = r.Height;
}
else
{
viewportOffset = _scrollData._computedOffset.X;
viewportSize = ViewportWidth;
targetRectOffset = r.X;
targetRectSize = r.Width;
}
targetRectOffset += viewportOffset;
minPhysicalOffset = ScrollContentPresenter.ComputeScrollOffsetWithMinimalScroll(
viewportOffset, viewportOffset + viewportSize, targetRectOffset, targetRectOffset + targetRectSize);
// Compute the visible rectangle of the child relative to the viewport.
double left = Math.Max(targetRectOffset, minPhysicalOffset);
targetRectSize = Math.Max(Math.Min(targetRectSize + targetRectOffset, minPhysicalOffset + viewportSize) - left, 0);
targetRectOffset = left;
targetRectOffset -= viewportOffset;
if (fHorizontal)
{
newOffset.Y = minPhysicalOffset;
newRect.Y = targetRectOffset;
newRect.Height = targetRectSize;
}
else
{
newOffset.X = minPhysicalOffset;
newRect.X = targetRectOffset;
newRect.Width = targetRectSize;
}
}
private void MakeVisibleLogicalHelper(int childIndex, ref Vector newOffset, ref Rect newRect)
{
bool fHorizontal = (Orientation == Orientation.Horizontal);
int firstChildInView;
int newFirstChild;
int viewportSize;
double childOffsetWithinViewport = 0;
if (fHorizontal)
{
firstChildInView = (int)_scrollData._computedOffset.X;
viewportSize = (int)_scrollData._viewport.Width;
}
else
{
firstChildInView = (int)_scrollData._computedOffset.Y;
viewportSize = (int)_scrollData._viewport.Height;
}
newFirstChild = firstChildInView;
// If the target child is before the current viewport, move the viewport to put the child at the top.
if (childIndex < firstChildInView)
{
newFirstChild = childIndex;
}
// If the target child is after the current viewport, move the viewport to put the child at the bottom.
else if (childIndex > firstChildInView + viewportSize - 1)
{
Size childDesiredSize = InternalChildren[childIndex].DesiredSize;
double nextChildSize = ((fHorizontal) ? childDesiredSize.Width : childDesiredSize.Height);
double viewportSpace = _scrollData._physicalViewport - nextChildSize;
int i = childIndex;
while (i > 0 && DoubleUtil.GreaterThanOrClose(viewportSpace, 0.0))
{
i--;
childDesiredSize = InternalChildren[i].DesiredSize;
nextChildSize = ((fHorizontal) ? childDesiredSize.Width : childDesiredSize.Height);
childOffsetWithinViewport += nextChildSize;
viewportSpace -= nextChildSize;
}
if (i != childIndex && DoubleUtil.LessThan(viewportSpace, 0.0))
{
childOffsetWithinViewport -= nextChildSize;
i++;
}
newFirstChild = i;
}
if (fHorizontal)
{
newOffset.X = newFirstChild;
newRect.X = childOffsetWithinViewport;
newRect.Width = InternalChildren[childIndex].DesiredSize.Width;
}
else
{
newOffset.Y = newFirstChild;
newRect.Y = childOffsetWithinViewport;
newRect.Height = InternalChildren[childIndex].DesiredSize.Height;
}
}
static private int CoerceOffsetToInteger(double offset, int numberOfItems)
{
int iNewOffset;
if (Double.IsNegativeInfinity(offset))
{
iNewOffset = 0;
}
else if (Double.IsPositiveInfinity(offset))
{
iNewOffset = numberOfItems - 1;
}
else
{
iNewOffset = (int)offset;
iNewOffset = Math.Max(Math.Min(numberOfItems - 1, iNewOffset), 0);
}
return iNewOffset;
}
//------------------------------------------------------------
// Avalon Property Callbacks/Overrides
//-----------------------------------------------------------
#region Avalon Property Callbacks/Overrides
///
///
///
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Since Orientation is so essential to logical scrolling/virtualization, we synchronously check if
// the new value is different and clear all scrolling data if so.
ResetScrolling(d as StackPanel);
}
#endregion
#endregion Private Methods
//------------------------------------------------------
//
// Private Properties
//
//-----------------------------------------------------
#region Private Properties
private bool IsScrolling
{
get { return (_scrollData != null) && (_scrollData._scrollOwner != null); }
}
//
// This property
// 1. Finds the correct initial size for the _effectiveValues store on the current DependencyObject
// 2. This is a performance optimization
//
internal override int EffectiveValuesInitialSize
{
get { return 9; }
}
#endregion Private Properties
//-----------------------------------------------------
//
// Private Fields
//
//-----------------------------------------------------
#region Private Fields
// Logical scrolling and virtualization data.
private ScrollData _scrollData;
#endregion Private Fields
//------------------------------------------------------
//
// Private Structures / Classes
//
//-----------------------------------------------------
#region Private Structures Classes
//------------------------------------------------------------
// ScrollData class
//------------------------------------------------------------
#region ScrollData
// Helper class to hold scrolling data.
// This class exists to reduce working set when StackPanel is used outside a scrolling situation.
// Standard "extra pointer always for less data sometimes" cache savings model:
// !Scroll [1xReference]
// Scroll [1xReference] + [6xDouble + 1xReference]
private class ScrollData
{
// Clears layout generated data.
// Does not clear scrollOwner, because unless resetting due to a scrollOwner change, we won't get reattached.
internal void ClearLayout()
{
_offset = new Vector();
_viewport = _extent = new Size();
_physicalViewport = 0;
}
// For Stack/Flow, the two dimensions of properties are in different units:
// 1. The "logically scrolling" dimension uses items as units.
// 2. The other dimension physically scrolls. Units are in Avalon pixels (1/96").
internal bool _allowHorizontal;
internal bool _allowVertical;
internal Vector _offset; // Scroll offset of content. Positive corresponds to a visually upward offset.
internal Vector _computedOffset = new Vector(0,0);
internal Size _viewport; // ViewportSize is in {pixels x items} (or vice-versa).
internal Size _extent; // Extent is the total number of children (logical dimension) or physical size
internal double _physicalViewport; // The physical size of the viewport for the items dimension above.
internal ScrollViewer _scrollOwner; // ScrollViewer to which we're attached.
}
#endregion ScrollData
#endregion Private Structures Classes
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- CellCreator.cs
- WebPartDisplayMode.cs
- SchemaMapping.cs
- RTLAwareMessageBox.cs
- DataStreamFromComStream.cs
- OletxEnlistment.cs
- SqlBuilder.cs
- ScriptResourceHandler.cs
- AttributeCollection.cs
- TextTreeObjectNode.cs
- UserControlAutomationPeer.cs
- InheritanceAttribute.cs
- SocketAddress.cs
- TextPointer.cs
- EntitySetBase.cs
- VirtualizingStackPanel.cs
- UnsafeNativeMethods.cs
- ValidationPropertyAttribute.cs
- IOThreadScheduler.cs
- translator.cs
- DataGridAutoGeneratingColumnEventArgs.cs
- ToolStripDropDownMenu.cs
- MimeTypePropertyAttribute.cs
- DbSourceParameterCollection.cs
- GeneralTransform.cs
- CachedPathData.cs
- XmlObjectSerializerReadContextComplexJson.cs
- HtmlFormWrapper.cs
- BitmapEffectInputData.cs
- CellTreeNodeVisitors.cs
- SqlParameterCollection.cs
- XmlSchemaAttribute.cs
- TargetControlTypeCache.cs
- SizeValueSerializer.cs
- PtsPage.cs
- FormView.cs
- BindStream.cs
- CellRelation.cs
- DecimalAnimationBase.cs
- Literal.cs
- DetailsViewCommandEventArgs.cs
- StateDesigner.TransitionInfo.cs
- X509ClientCertificateAuthentication.cs
- Util.cs
- PreProcessor.cs
- CacheChildrenQuery.cs
- ToolZoneDesigner.cs
- WebErrorHandler.cs
- HttpStreams.cs
- NamespaceMapping.cs
- SqlUtils.cs
- ConfigurationPropertyAttribute.cs
- recordstate.cs
- TransformPatternIdentifiers.cs
- _LazyAsyncResult.cs
- ParallelTimeline.cs
- QueryOutputWriterV1.cs
- DateTimePicker.cs
- SemanticValue.cs
- TripleDESCryptoServiceProvider.cs
- PeerNameRecordCollection.cs
- ObjectStateManager.cs
- VisualTreeHelper.cs
- PaintValueEventArgs.cs
- storepermissionattribute.cs
- RemoveStoryboard.cs
- HostUtils.cs
- BulletedListEventArgs.cs
- Perspective.cs
- DataServiceConfiguration.cs
- DataListItem.cs
- MultipartIdentifier.cs
- MailAddressCollection.cs
- XmlQueryRuntime.cs
- DataSetUtil.cs
- AppLevelCompilationSectionCache.cs
- Font.cs
- ActiveXHost.cs
- DbUpdateCommandTree.cs
- WebAdminConfigurationHelper.cs
- DesignerVerb.cs
- DoubleLinkListEnumerator.cs
- ASCIIEncoding.cs
- SqlWebEventProvider.cs
- VBCodeProvider.cs
- ScalarOps.cs
- ColumnClickEvent.cs
- OracleDataAdapter.cs
- TextDecorationUnitValidation.cs
- CloseSequence.cs
- EventListener.cs
- JournalNavigationScope.cs
- UserCancellationException.cs
- SmtpCommands.cs
- DictionaryBase.cs
- BaseResourcesBuildProvider.cs
- QilInvokeEarlyBound.cs
- TempEnvironment.cs
- FormViewInsertedEventArgs.cs
- MimeMapping.cs