From 4dda72bb891ab0525edfb3eaa6b1e31428898b74 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Mon, 7 Feb 2022 18:14:15 +0530 Subject: [PATCH] fix hang when margin is not a multiple of DPI rounding quantum (#5841) (#6004) Co-authored-by: Sam Bent --- .../System/Windows/Controls/ItemsControl.cs | 32 ++++++++++++ .../Controls/VirtualizingStackPanel.cs | 49 ++++++++++++------- 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/ItemsControl.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/ItemsControl.cs index 76eefa9db..9f462e26b 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/ItemsControl.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/ItemsControl.cs @@ -3079,6 +3079,38 @@ private bool IsOnCurrentPage(FrameworkElement viewPort, FrameworkElement element return ElementViewportPosition.None; } + // this version also returns the element's layout rectangle (in viewport's coordinates). + // VirtualizingStackPanel needs this, to determine the element's scroll offset. + internal static ElementViewportPosition GetElementViewportPosition(FrameworkElement viewPort, + UIElement element, + FocusNavigationDirection axis, + bool fullyVisible, + bool ignorePerpendicularAxis, + out Rect elementRect, + out Rect layoutRect) + { + ElementViewportPosition position = GetElementViewportPosition( + viewPort, + element, + axis, + fullyVisible, + false, + out elementRect); + + if (position == ElementViewportPosition.None) + { + layoutRect = Rect.Empty; + } + else + { + Visual parent = VisualTreeHelper.GetParent(element) as Visual; + Debug.Assert(element != viewPort && element.IsArrangeValid && parent != null, "GetElementViewportPosition called in unsupported situation"); + layoutRect = CorrectCatastrophicCancellation(parent.TransformToAncestor(viewPort)).TransformBounds(element.PreviousArrangeRect); + } + + return position; + } + // in large virtualized hierarchical lists (TreeView or grouping), the transform // returned by element.TransformToAncestor(viewport) is vulnerable to catastrophic // cancellation. If element is at the top of the viewport, but embedded in diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/VirtualizingStackPanel.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/VirtualizingStackPanel.cs index 0ae813c7d..17c76adc7 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/VirtualizingStackPanel.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/VirtualizingStackPanel.cs @@ -1189,6 +1189,7 @@ private void ClearAnchorInformation(bool shouldAbort) if (fe.IsVisible) { Rect elementRect; + Rect layoutRect; // get the vp-position of the element, ignoring the secondary axis // (DevDiv2 1136036, 1203626 show two different cases why we @@ -1201,7 +1202,8 @@ private void ClearAnchorInformation(bool shouldAbort) direction, false /*fullyVisible*/, !isVSP45Compat /*ignorePerpendicularAxis*/, - out elementRect); + out elementRect, + out layoutRect); if (elementPosition == ElementViewportPosition.PartiallyInViewport || elementPosition == ElementViewportPosition.CompletelyInViewport) @@ -1287,20 +1289,35 @@ private void ClearAnchorInformation(bool shouldAbort) { if (direction == FocusNavigationDirection.Down) { - firstContainerOffsetFromViewport = elementRect.Y; - if (!isVSP45Compat) - { - firstContainerOffsetFromViewport -= fe.Margin.Top; - } - } + if (isVSP45Compat) + { + firstContainerOffsetFromViewport = elementRect.Y; + } + else + { + // include the leading margin in the offset. Simply subtracting + // the margin doesn't work when layout rounding is in effect, as + // we can't deduce how rounding affected the arrangement of the + // element and its margin. Instead, just use the layout rect directly. + firstContainerOffsetFromViewport = layoutRect.Top; + } + } else // (direction == FocusNavigationDirection.Right) { - firstContainerOffsetFromViewport = elementRect.X; - if (!isVSP45Compat) - { - firstContainerOffsetFromViewport -= fe.Margin.Left; - } - } + + if (isVSP45Compat) + { + firstContainerOffsetFromViewport = elementRect.X; + } + else + { + // include the leading margin in the offset. Simply subtracting + // the margin doesn't work when layout rounding is in effect, as + // we can't deduce how rounding affected the arrangement of the + // element and its margin. Instead, just use the layout rect directly. + firstContainerOffsetFromViewport = layoutRect.Left; + } + } } else if (findTopContainer && isTopContainer) { @@ -1931,7 +1948,7 @@ public ScrollViewer ScrollOwner } set { - if (_scrollData == null) EnsureScrollData(); + EnsureScrollData(); if (value != _scrollData._scrollOwner) { ResetScrolling(this); @@ -9510,10 +9527,6 @@ private int ItemCount private void EnsureScrollData() { if (_scrollData == null) { _scrollData = new ScrollData(); } - else - { - Debug.Assert(_scrollData._scrollOwner != null, "Scrolling an unconnected VSP"); - } } private static void ResetScrolling(VirtualizingStackPanel element) -- GitLab