-
Notifications
You must be signed in to change notification settings - Fork 2k
[Windows] Fix COMException when restoring a ScrollView as ContentPage.Content after swapping it out #35360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Windows] Fix COMException when restoring a ScrollView as ContentPage.Content after swapping it out #35360
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| namespace Maui.Controls.Sample.Issues; | ||
|
|
||
| [Issue(IssueTracker.Github, 35277, "COMException when restoring a page content after swapping it out", PlatformAffected.UWP)] | ||
| public class Issue35277 : ContentPage | ||
| { | ||
| ScrollView _originalScrollView; | ||
|
|
||
| public Issue35277() | ||
| { | ||
| var swapButton = new Button | ||
| { | ||
| Text = "Swap and Restore", | ||
| HorizontalOptions = LayoutOptions.Center, | ||
| AutomationId = "SwapAndRestoreButton" | ||
| }; | ||
| swapButton.Clicked += OnSwapAndRestoreClicked; | ||
|
|
||
| _originalScrollView = new ScrollView | ||
| { | ||
| Content = new VerticalStackLayout | ||
| { | ||
| Spacing = 20, | ||
| VerticalOptions = LayoutOptions.Center, | ||
| Children = | ||
| { | ||
| swapButton, | ||
| new Label | ||
| { | ||
| Text = "Original Content", | ||
| HorizontalOptions = LayoutOptions.Center, | ||
| AutomationId = "OriginalLabel" | ||
| } | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| Content = _originalScrollView; | ||
| } | ||
|
|
||
| void OnSwapAndRestoreClicked(object sender, EventArgs e) | ||
| { | ||
| var savedScrollView = _originalScrollView; | ||
| Content = new Label | ||
| { | ||
| Text = "Temporary Content", | ||
| HorizontalOptions = LayoutOptions.Center, | ||
| VerticalOptions = LayoutOptions.Center, | ||
| AutomationId = "TemporaryLabel" | ||
| }; | ||
|
|
||
| Microsoft.Maui.ApplicationModel.MainThread.BeginInvokeOnMainThread(() => | ||
| { | ||
| Content = savedScrollView; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| using NUnit.Framework; | ||
| using UITest.Appium; | ||
| using UITest.Core; | ||
|
|
||
| namespace Microsoft.Maui.TestCases.Tests.Issues; | ||
|
|
||
| public class Issue35277 : _IssuesUITest | ||
| { | ||
| public Issue35277(TestDevice device) : base(device) { } | ||
|
|
||
| public override string Issue => "COMException when restoring a page content after swapping it out"; | ||
|
|
||
| [Test] | ||
| [Category(UITestCategories.ScrollView)] | ||
| public void ScrollViewContentShouldRestoreWithoutCOMException() | ||
| { | ||
| App.WaitForElement("SwapAndRestoreButton"); | ||
| App.Tap("SwapAndRestoreButton"); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [moderate] Test Coverage — The test only re-finds |
||
| // If no COMException is thrown, the original ScrollView content (with the button) restores successfully | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Weak assertion. The test only verifies the button is still findable after the swap+restore. On platforms where the bug never reproduces (Android/iOS/macOS), the test will pass trivially. Add |
||
| App.WaitForElement("SwapAndRestoreButton"); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -149,20 +149,23 @@ static void UpdateContentPanel(IScrollView scrollView, IScrollViewHandler handle | |
| { | ||
| currentPaddingLayer.CachedChildren.Clear(); | ||
| } | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| if (currentPaddingLayer is not null) | ||
| { | ||
| currentPaddingLayer.CachedChildren.Clear(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Order is correct, but worth a comment. The |
||
| } | ||
|
|
||
| // Detach the old handler if it exists (prevents WinUI COM exception on reuse) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [minor] Performance / Hot-Path Side Effects — Removing the |
||
| scrollView.PresentedContent.Handler?.DisconnectHandler(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unconditional handler churn. Removing the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Threading. |
||
|
|
||
| var nativeContent = scrollView.PresentedContent.ToPlatform(handler.MauiContext); | ||
|
|
||
| if (currentPaddingLayer is not null) | ||
| { | ||
| // Only update if content has changed or is missing | ||
| if (currentPaddingLayer.CachedChildren.Count == 0 || currentPaddingLayer.CachedChildren[0] != nativeContent) | ||
| { | ||
| currentPaddingLayer.CachedChildren.Clear(); | ||
| currentPaddingLayer.CachedChildren.Add(nativeContent); | ||
| } | ||
| currentPaddingLayer.CachedChildren.Add(nativeContent); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [minor] Documentation / Traceability — The comment justifies the |
||
| } | ||
| else | ||
| { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[minor] Null Annotations —
_originalScrollViewis declared asScrollView(non-nullable) without nullable annotation context but is assigned in the constructor. Fine as-is; flag only because the field could trivially be markedreadonlysince it is never reassigned (only itsContentis via the parent page swap).