Skip to content

Instantly share code, notes, and snippets.

Created January 10, 2012 21:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/1591233 to your computer and use it in GitHub Desktop.
Save anonymous/1591233 to your computer and use it in GitHub Desktop.
Comments to some WPF sourcces
Код UIElement:
/// <summary>
/// Updates DesiredSize of the UIElement. Must be called by parents from theor MeasureCore, to form recursive update.
/// This is first pass of layout update.
/// </summary>
/// <remarks>
/// Measure is called by parents on their children. Internally, Measure calls MeasureCore override on the same object,
/// giving it opportunity to compute its DesiredSize.<para/>
/// This method will return immediately if child is not Dirty, previously measured
/// and availableSize is the same as cached. <para/>
/// This method also resets the IsMeasureinvalid bit on the child.<para/>
/// In case when "unbounded measure to content" is needed, parent can use availableSize
/// as double.PositiveInfinity. Any returned size is OK in this case.
/// </remarks>
/// <param name="availableSize">Available size that parent can give to the child. May be infinity (when parent wants to
/// measure to content). This is soft constraint. Child can return bigger size to indicate that it wants bigger space and hope
/// that parent can throw in scrolling...</param>
public void Measure(Size availableSize) {
// ...
}
/// <summary>
/// Parents or system call this method to arrange the internals of children on a second pass of layout update.
/// </summary>
/// <remarks>
/// This method internally calls ArrangeCore override, giving the derived class opportunity
/// to arrange its children and/or content using final computed size.
/// In their ArrangeCore overrides, derived class is supposed to create its visual structure and
/// prepare itself for rendering. Arrange is called by parents
/// from their implementation of ArrangeCore or by system when needed.
/// This method sets Bounds=finalSize before calling ArrangeCore.
/// </remarks>
/// <param name="finalRect">This is the final size and location that parent or system wants this UIElement to assume.</param>
public void Arrange(Rect finalRect) {
// ...
}
Код FrameworkElement :
/// <summary>
/// Measurement override. Implement your size-to-content logic here.
/// </summary>
/// <remarks>
/// MeasureOverride is designed to be the main customizability point for size control of layout.
/// Element authors should override this method, call Measure on each child element,
/// and compute their desired size based upon the measurement of the children.
/// The return value should be the desired size.<para/>
/// Note: It is required that a parent element calls Measure on each child or they won't be sized/arranged.
/// Typical override follows a pattern roughly like this (pseudo-code):
/// <example>
/// <code lang="C#">
/// <![CDATA[
///
/// protected override Size MeasureOverride(Size availableSize)
/// {
/// foreach (UIElement child in VisualChildren)
/// {
/// child.Measure(availableSize);
/// availableSize.Deflate(child.DesiredSize);
/// }
///
/// Size desired = ... compute sum of children's DesiredSize ...;
/// return desired;
/// }
/// ]]>
/// </code>
/// </example>
/// The key aspects of this snippet are:
/// <list type="bullet">
/// <item>You must call Measure on each child element</item>
/// <item>It is common to cache measurement information between the MeasureOverride and ArrangeOverride method calls</item>
/// <item>Calling base.MeasureOverride is not required.</item>
/// <item>Calls to Measure on children are passing either the same availableSize as the parent, or a subset of the area depending
/// on the type of layout the parent will perform (for example, it would be valid to remove the area
/// for some border or padding).</item>
/// </list>
/// </remarks>
/// <param name="availableSize">Available size that parent can give to the child. May be infinity (when parent wants to
/// measure to content). This is soft constraint. Child can return bigger size to indicate that it wants bigger space and hope
/// that parent can throw in scrolling...</param>
/// <returns>Desired Size of the control, given available size passed as parameter.</returns>
protected virtual Size MeasureOverride(Size availableSize)
{
return new Size(0,0);
}
/// <summary>
/// ArrangeOverride allows for the customization of the positioning of children.
/// </summary>
/// <remarks>
/// Element authors should override this method, call Arrange on each visible child element,
/// passing final size for each child element via finalSize parameter.
/// Note: It is required that a parent element calls Arrange on each child or they won't be rendered.
/// Typical override follows a pattern roughly like this (pseudo-code):
/// <example>
/// <code lang="C#">
/// <![CDATA[
///
///
/// protected override Size ArrangeOverride(Size finalSize)
/// {
/// foreach (UIElement child in VisualChildren)
/// {
/// child.Arrange(new Rect(childX, childY, childFinalSize));
/// }
/// return finalSize; //this can be another size if the panel actually takes smaller/larger then finalSize
/// }
/// ]]>
/// </code>
/// </example>
/// </remarks>
/// <param name="finalSize">The final size that element should use to arrange itself and its children.</param>
/// <returns>The size that element actually is going to use for rendering. If this size is not the same as finalSize
/// input parameter, the AlignmentX/AlignmentY properties will position the ink rect of the element
/// appropriately.</returns>
protected virtual Size ArrangeOverride(Size finalSize)
{
return finalSize;
}
/// <summary>
/// Override for <seealso cref="UIElement.MeasureCore" />.
/// </summary>
protected sealed override Size MeasureCore(Size availableSize)
{
Debug.Assert(MeasureData == null || availableSize == MeasureData.AvailableSize, "MeasureData needs to be passed down in [....] with size");
// If using layout rounding, check whether rounding needs to compensate for high DPI
bool useLayoutRounding = this.UseLayoutRounding;
if (useLayoutRounding)
{
if (!CheckFlagsAnd(VisualFlags.UseLayoutRounding))
{
this.SetFlags(true, VisualFlags.UseLayoutRounding);
}
}
//build the visual tree from styles first
ApplyTemplate();
if (BypassLayoutPolicies)
{
return MeasureOverride(availableSize);
}
else
{
Thickness margin = Margin;
double marginWidth = margin.Left + margin.Right;
double marginHeight = margin.Top + margin.Bottom;
MeasureData measureData = MeasureData;
// parent size is what parent want us to be
Size frameworkAvailableSize = new Size(
Math.Max(availableSize.Width - marginWidth, 0),
Math.Max(availableSize.Height - marginHeight, 0));
MinMax mm = new MinMax(this);
LayoutTransformData ltd = LayoutTransformDataField.GetValue(this);
{
Transform layoutTransform = this.LayoutTransform;
// check that LayoutTransform is non-trivial
if (layoutTransform != null && !layoutTransform.IsIdentity)
{
if (ltd == null)
{
// allocate and store ltd if needed
ltd = new LayoutTransformData();
LayoutTransformDataField.SetValue(this, ltd);
}
ltd.CreateTransformSnapshot(layoutTransform);
ltd.UntransformedDS = new Size();
if (useLayoutRounding)
{
ltd.TransformedUnroundedDS = new Size();
}
}
else if (ltd != null)
{
// clear ltd storage
ltd = null;
LayoutTransformDataField.ClearValue(this);
}
}
if (ltd != null)
{
// Find the maximal area rectangle in local (child) space that we can fit, post-transform
// in the decorator's measure constraint.
frameworkAvailableSize = FindMaximalAreaLocalSpaceRect(ltd.Transform, frameworkAvailableSize);
}
frameworkAvailableSize.Width = Math.Max(mm.minWidth, Math.Min(frameworkAvailableSize.Width, mm.maxWidth));
frameworkAvailableSize.Height = Math.Max(mm.minHeight, Math.Min(frameworkAvailableSize.Height, mm.maxHeight));
// If layout rounding is enabled, round available size passed to MeasureOverride.
if (useLayoutRounding)
{
frameworkAvailableSize = UIElement.RoundLayoutSize(frameworkAvailableSize, FrameworkElement.DpiScaleX, FrameworkElement.DpiScaleY);
}
// call to specific layout to measure
if (measureData != null)
{
measureData.AvailableSize = frameworkAvailableSize;
}
Size desiredSize = MeasureOverride(frameworkAvailableSize);
if (measureData != null)
{
// MeasureData should be treated like a parameter to Measure and thus not modified when returning from this call.
measureData.AvailableSize = availableSize;
}
// maximize desiredSize with user provided min size
desiredSize = new Size(
Math.Max(desiredSize.Width, mm.minWidth),
Math.Max(desiredSize.Height, mm.minHeight));
//here is the "true minimum" desired size - the one that is
//for sure enough for the control to render its content.
Size unclippedDesiredSize = desiredSize;
if (ltd != null)
{
//need to store unclipped, untransformed desired size to be able to arrange later
ltd.UntransformedDS = unclippedDesiredSize;
//transform unclipped desired size
Rect unclippedBoundsTransformed = Rect.Transform(new Rect(0, 0, unclippedDesiredSize.Width, unclippedDesiredSize.Height), ltd.Transform.Value);
unclippedDesiredSize.Width = unclippedBoundsTransformed.Width;
unclippedDesiredSize.Height = unclippedBoundsTransformed.Height;
}
bool clipped = false;
// User-specified max size starts to "clip" the control here.
//Starting from this point desiredSize could be smaller then actually
//needed to render the whole control
if (desiredSize.Width > mm.maxWidth)
{
desiredSize.Width = mm.maxWidth;
clipped = true;
}
if (desiredSize.Height > mm.maxHeight)
{
desiredSize.Height = mm.maxHeight;
clipped = true;
}
//transform desired size to layout slot space
if (ltd != null)
{
Rect childBoundsTransformed = Rect.Transform(new Rect(0, 0, desiredSize.Width, desiredSize.Height), ltd.Transform.Value);
desiredSize.Width = childBoundsTransformed.Width;
desiredSize.Height = childBoundsTransformed.Height;
}
// because of negative margins, clipped desired size may be negative.
// need to keep it as doubles for that reason and maximize with 0 at the
// very last point - before returning desired size to the parent.
double clippedDesiredWidth = desiredSize.Width + marginWidth;
double clippedDesiredHeight = desiredSize.Height + marginHeight;
// In overconstrained scenario, parent wins and measured size of the child,
// including any sizes set or computed, can not be larger then
// available size. We will clip the guy later.
if (clippedDesiredWidth > availableSize.Width)
{
clippedDesiredWidth = availableSize.Width;
clipped = true;
}
if (clippedDesiredHeight > availableSize.Height)
{
clippedDesiredHeight = availableSize.Height;
clipped = true;
}
// Set transformed, unrounded size on layout transform, if any.
if (ltd != null)
{
ltd.TransformedUnroundedDS = new Size(Math.Max(0, clippedDesiredWidth), Math.Max(0, clippedDesiredHeight));
}
// If using layout rounding, round desired size.
if (useLayoutRounding)
{
clippedDesiredWidth = UIElement.RoundLayoutValue(clippedDesiredWidth, DpiScaleX);
clippedDesiredHeight = UIElement.RoundLayoutValue(clippedDesiredHeight, DpiScaleY);
}
// Note: unclippedDesiredSize is needed in ArrangeCore,
// because due to the layout protocol, arrange should be called
// with constraints greater or equal to child's desired size
// returned from MeasureOverride. But in most circumstances
// it is possible to reconstruct original unclipped desired size.
// In such cases we want to optimize space and save 16 bytes by
// not storing it on each FrameworkElement.
//
// The if statement conditions below lists the cases when
// it is NOT possible to recalculate unclipped desired size later
// in ArrangeCore, thus we save it into Uncommon Fields...
//
// Note 2: use SizeBox to avoid CLR boxing of Size.
// measurements show it is better to allocate an object once than
// have spurious boxing allocations on every resize
SizeBox sb = UnclippedDesiredSizeField.GetValue(this);
if (clipped
|| clippedDesiredWidth < 0
|| clippedDesiredHeight < 0)
{
if (sb == null) //not yet allocated, allocate the box
{
sb = new SizeBox(unclippedDesiredSize);
UnclippedDesiredSizeField.SetValue(this, sb);
}
else //we already have allocated size box, simply change it
{
sb.Width = unclippedDesiredSize.Width;
sb.Height = unclippedDesiredSize.Height;
}
}
else
{
if (sb != null)
UnclippedDesiredSizeField.ClearValue(this);
}
return new Size(Math.Max(0, clippedDesiredWidth), Math.Max(0, clippedDesiredHeight));
}
}
/// <summary>
/// Override for <seealso cref="UIElement.ArrangeCore" />.
/// </summary>
protected sealed override void ArrangeCore(Rect finalRect)
{
// If using layout rounding, check whether rounding needs to compensate for high DPI
bool useLayoutRounding = this.UseLayoutRounding;
LayoutTransformData ltd = LayoutTransformDataField.GetValue(this);
Size transformedUnroundedDS = Size.Empty;
if (useLayoutRounding)
{
if (!CheckFlagsAnd(VisualFlags.UseLayoutRounding))
{
SetFlags(true, VisualFlags.UseLayoutRounding);
}
}
if (BypassLayoutPolicies)
{
Size oldRenderSize = RenderSize;
Size inkSize = ArrangeOverride(finalRect.Size);
RenderSize = inkSize;
SetLayoutOffset(new Vector(finalRect.X, finalRect.Y), oldRenderSize);
}
else
{
// If LayoutConstrained==true (parent wins in layout),
// we might get finalRect.Size smaller then UnclippedDesiredSize.
// Stricltly speaking, this may be the case even if LayoutConstrained==false (child wins),
// since who knows what a particualr parent panel will try to do in error.
// In this case we will not actually arrange a child at a smaller size,
// since the logic of the child does not expect to receive smaller size
// (if it coudl deal with smaller size, it probably would accept it in MeasureOverride)
// so lets replace the smaller arreange size with UnclippedDesiredSize
// and then clip the guy later.
// We will use at least UnclippedDesiredSize to compute arrangeSize of the child, and
// we will use layoutSlotSize to compute alignments - so the bigger child can be aligned within
// smaller slot.
// This is computed on every ArrangeCore. Depending on LayoutConstrained, actual clip may apply or not
NeedsClipBounds = false;
// Start to compute arrange size for the child.
// It starts from layout slot or deisred size if layout slot is smaller then desired,
// and then we reduce it by margins, apply Width/Height etc, to arrive at the size
// that child will get in its ArrangeOverride.
Size arrangeSize = finalRect.Size;
Thickness margin = Margin;
double marginWidth = margin.Left + margin.Right;
double marginHeight = margin.Top + margin.Bottom;
arrangeSize.Width = Math.Max(0, arrangeSize.Width - marginWidth);
arrangeSize.Height = Math.Max(0, arrangeSize.Height - marginHeight);
// First, get clipped, transformed, unrounded size.
if (useLayoutRounding)
{
if (ltd != null && ltd.TransformedUnroundedDS != null)
{
transformedUnroundedDS = ltd.TransformedUnroundedDS;
transformedUnroundedDS.Width = Math.Max(0, transformedUnroundedDS.Width - marginWidth);
transformedUnroundedDS.Height = Math.Max(0, transformedUnroundedDS.Height- marginHeight);
}
}
// Next, compare against unclipped, transformed size.
SizeBox sb = UnclippedDesiredSizeField.GetValue(this);
Size unclippedDesiredSize;
if (sb == null)
{
unclippedDesiredSize = new Size(Math.Max(0, this.DesiredSize.Width - marginWidth),
Math.Max(0, this.DesiredSize.Height - marginHeight));
// There is no unclipped desired size, so check against clipped, but unrounded DS.
if (transformedUnroundedDS != Size.Empty)
{
unclippedDesiredSize.Width = Math.Max(transformedUnroundedDS.Width, unclippedDesiredSize.Width);
unclippedDesiredSize.Height = Math.Max(transformedUnroundedDS.Height, unclippedDesiredSize.Height);
}
}
else
{
unclippedDesiredSize = new Size(sb.Width, sb.Height);
}
if (DoubleUtil.LessThan(arrangeSize.Width, unclippedDesiredSize.Width))
{
NeedsClipBounds = true;
arrangeSize.Width = unclippedDesiredSize.Width;
}
if (DoubleUtil.LessThan(arrangeSize.Height, unclippedDesiredSize.Height))
{
NeedsClipBounds = true;
arrangeSize.Height = unclippedDesiredSize.Height;
}
// Alignment==Stretch --> arrange at the slot size minus margins
// Alignment!=Stretch --> arrange at the unclippedDesiredSize
if (HorizontalAlignment != HorizontalAlignment.Stretch)
{
arrangeSize.Width = unclippedDesiredSize.Width;
}
if (VerticalAlignment != VerticalAlignment.Stretch)
{
arrangeSize.Height = unclippedDesiredSize.Height;
}
//if LayoutTransform is set, arrange at untransformed DS always
//alignments apply to the BoundingBox after transform
if (ltd != null)
{
// Repeat the measure-time algorithm for finding a best fit local rect.
// This essentially implements Stretch in case of LayoutTransform
Size potentialArrangeSize = FindMaximalAreaLocalSpaceRect(ltd.Transform, arrangeSize);
arrangeSize = potentialArrangeSize;
// If using layout rounding, round untransformed desired size - in MeasureCore, this value is first transformed and clipped
// before rounding, and hence saved unrounded.
unclippedDesiredSize = ltd.UntransformedDS;
//only use max area rect if both dimensions of it are larger then
//desired size - replace with desired size otherwise
if (!DoubleUtil.IsZero(potentialArrangeSize.Width)
&& !DoubleUtil.IsZero(potentialArrangeSize.Height))
{
//Use less precise comparision - otherwise FP jitter may cause drastic jumps here
if (LayoutDoubleUtil.LessThan(potentialArrangeSize.Width, unclippedDesiredSize.Width)
|| LayoutDoubleUtil.LessThan(potentialArrangeSize.Height, unclippedDesiredSize.Height))
{
arrangeSize = unclippedDesiredSize;
}
}
//if pre-transformed into local space arrangeSize is smaller in any dimension then
//unclipped local DesiredSize of the element, extend the arrangeSize but
//remember that we potentially need to clip the result of such arrange.
if (DoubleUtil.LessThan(arrangeSize.Width, unclippedDesiredSize.Width))
{
NeedsClipBounds = true;
arrangeSize.Width = unclippedDesiredSize.Width;
}
if (DoubleUtil.LessThan(arrangeSize.Height, unclippedDesiredSize.Height))
{
NeedsClipBounds = true;
arrangeSize.Height = unclippedDesiredSize.Height;
}
}
MinMax mm = new MinMax(this);
//we have to choose max between UnclippedDesiredSize and Max here, because
//otherwise setting of max property could cause arrange at less then unclippedDS.
//Clipping by Max is needed to limit stretch here
double effectiveMaxWidth = Math.Max(unclippedDesiredSize.Width, mm.maxWidth);
if (DoubleUtil.LessThan(effectiveMaxWidth, arrangeSize.Width))
{
NeedsClipBounds = true;
arrangeSize.Width = effectiveMaxWidth;
}
double effectiveMaxHeight = Math.Max(unclippedDesiredSize.Height, mm.maxHeight);
if (DoubleUtil.LessThan(effectiveMaxHeight, arrangeSize.Height))
{
NeedsClipBounds = true;
arrangeSize.Height = effectiveMaxHeight;
}
// If using layout rounding, round size passed to children.
if (useLayoutRounding)
{
arrangeSize = UIElement.RoundLayoutSize(arrangeSize, DpiScaleX, DpiScaleY);
}
Size oldRenderSize = RenderSize;
Size innerInkSize = ArrangeOverride(arrangeSize);
//Here we use un-clipped InkSize because element does not know that it is
//clipped by layout system and it shoudl have as much space to render as
//it returned from its own ArrangeOverride
RenderSize = innerInkSize;
if (useLayoutRounding)
{
RenderSize = UIElement.RoundLayoutSize(RenderSize, DpiScaleX, DpiScaleY);
}
//clippedInkSize differs from InkSize only what MaxWidth/Height explicitly clip the
//otherwise good arrangement. For ex, DS<clientSize but DS>MaxWidth - in this
//case we should initiate clip at MaxWidth and only show Top-Left portion
//of the element limited by Max properties. It is Top-left because in case when we
//are clipped by container we also degrade to Top-Left, so we are consistent.
Size clippedInkSize = new Size(Math.Min(innerInkSize.Width, mm.maxWidth),
Math.Min(innerInkSize.Height, mm.maxHeight));
if (useLayoutRounding)
{
clippedInkSize = UIElement.RoundLayoutSize(clippedInkSize, DpiScaleX, DpiScaleY);
}
//remember we have to clip if Max properties limit the inkSize
NeedsClipBounds |=
DoubleUtil.LessThan(clippedInkSize.Width, innerInkSize.Width)
|| DoubleUtil.LessThan(clippedInkSize.Height, innerInkSize.Height);
//if LayoutTransform is set, get the "outer bounds" - the alignments etc work on them
if (ltd != null)
{
Rect inkRectTransformed = Rect.Transform(new Rect(0, 0, clippedInkSize.Width, clippedInkSize.Height), ltd.Transform.Value);
clippedInkSize.Width = inkRectTransformed.Width;
clippedInkSize.Height = inkRectTransformed.Height;
if (useLayoutRounding)
{
clippedInkSize = UIElement.RoundLayoutSize(clippedInkSize, DpiScaleX, DpiScaleY);
}
}
//Note that inkSize now can be bigger then layoutSlotSize-margin (because of layout
//squeeze by the parent or LayoutConstrained=true, which clips desired size in Measure).
// The client size is the size of layout slot decreased by margins.
// This is the "window" through which we see the content of the child.
// Alignments position ink of the child in this "window".
// Max with 0 is neccessary because layout slot may be smaller then unclipped desired size.
Size clientSize = new Size(Math.Max(0, finalRect.Width - marginWidth),
Math.Max(0, finalRect.Height - marginHeight));
if (useLayoutRounding)
{
clientSize = UIElement.RoundLayoutSize(clientSize, DpiScaleX, DpiScaleY);
}
//remember we have to clip if clientSize limits the inkSize
NeedsClipBounds |=
DoubleUtil.LessThan(clientSize.Width, clippedInkSize.Width)
|| DoubleUtil.LessThan(clientSize.Height, clippedInkSize.Height);
Vector offset = ComputeAlignmentOffset(clientSize, clippedInkSize);
offset.X += finalRect.X + margin.Left;
offset.Y += finalRect.Y + margin.Top;
// If using layout rounding, round offset.
if (useLayoutRounding)
{
offset.X = UIElement.RoundLayoutValue(offset.X, DpiScaleX);
offset.Y = UIElement.RoundLayoutValue(offset.Y, DpiScaleY);
}
SetLayoutOffset(offset, oldRenderSize);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment