Skip to content
69 changes: 69 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue36298.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 36298, "[Windows] ContentPresenter throws ArgumentException when dynamically switching RefreshView or ScrollView content.", PlatformAffected.UWP)]
public class Issue36298 : ContentPage
{
ContentView _contentHolder;
View _view1;
View _view2;

public Issue36298()
{
_view1 = new ContentView
{
Content = new RefreshView
{
Content = new Label { Text = "View 1 - RefreshView" }
}
};

_view2 = new ContentView
{
Content = new ScrollView
{
Content = new Label { Text = "View 2 - ScrollView" }
}
};

_contentHolder = new ContentView

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] Regression test coverage — The regression title and production fix target ContentPresenter/IContentView reparenting, but the sample host here is a ContentView. That exercises a different setup than the reported ContentPresenter/templated-content scenario, and the direct ContentView swap can succeed without proving the ContentPresenter crash is fixed. Please make the repro use a ContentPresenter (for example via a control template) and verify that it fails on the old handler code and passes with this fix.

{
Content = _view1
};

var switchToView2Button = new Button
{
Text = "Switch to View 2",
AutomationId = "SwitchToView2"
};
switchToView2Button.Clicked += (_, _) => _contentHolder.Content = _view2;

var switchToView1Button = new Button
{
Text = "Switch to View 1",
AutomationId = "SwitchToView1"
};
switchToView1Button.Clicked += (_, _) => _contentHolder.Content = _view1;

var successLabel = new Label
{
Text = "Waiting",
AutomationId = "SuccessLabel"
};

switchToView2Button.Clicked += (_, _) => successLabel.Text = "View2";
switchToView1Button.Clicked += (_, _) => successLabel.Text = "Success";

Content = new VerticalStackLayout
{
Spacing = 10,
Padding = new Thickness(20),
Children =
{
switchToView2Button,
switchToView1Button,
_contentHolder,
successLabel
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue36298 : _IssuesUITest
{
public Issue36298(TestDevice device) : base(device) { }

public override string Issue => "[Windows] ContentPresenter throws ArgumentException when dynamically switching RefreshView or ScrollView content.";

[Test]
[Category(UITestCategories.Layout)]
public void SwitchingContentPresenterContentShouldNotCrash()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] Regression Prevention and Test Coverage — This PR changes both ContentViewHandler.Windows.cs and BorderHandler.Windows.cs, but the added UI test only exercises the ContentView path (via _contentHolder in the HostApp page); it never places the switched content inside a Border. The near-duplicate fix in BorderHandler.Windows.cs therefore ships with zero regression-test coverage — add a Border-based scenario (or parameterize this test) so a regression in the Border code path would actually be caught.

{
// Wait for the page to load showing View 1
App.WaitForElement("SwitchToView2");

// Switch to View 2
App.Tap("SwitchToView2");
App.WaitForElement("SwitchToView1");

Comment on lines +20 to +23
// Switch back to View 1 — this triggered the ArgumentException before the fix
Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
App.Tap("SwitchToView1");

// Label is updated to "Success" only if the switch completed without crashing.
// WaitForElement("SuccessLabel") alone is insufficient because the label
// is present from page load; we must verify the text was actually updated.
App.WaitForElement("SuccessLabel");
Assert.That(App.FindElement("SuccessLabel").GetText(), Is.EqualTo("Success"));
Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
Outdated
}
}
19 changes: 15 additions & 4 deletions src/Core/src/Handlers/Border/BorderHandler.Windows.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Microsoft.UI.Xaml;

namespace Microsoft.Maui.Handlers
{
Expand All @@ -20,14 +21,24 @@ static partial void UpdateContent(IBorderHandler handler)
_ = handler.VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class.");
_ = handler.MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class.");

handler.PlatformView.CachedChildren.Clear();
handler.PlatformView.EnsureBorderPath();

if (handler.VirtualView.PresentedContent is IView view)
{
// Detach the old handler if it exists (prevents WinUI COM exception on reuse)
view.Handler?.DisconnectHandler();
handler.PlatformView.Content = view.ToPlatform(handler.MauiContext);
var platformView = view.ToPlatform(handler.MauiContext);

// Detach from existing parent — mirrors Android RemoveFromParent / iOS RemoveFromSuperview
if (platformView is FrameworkElement fwElement &&
fwElement.Parent is MauiPanel existingParent)
{
existingParent.CachedChildren.Remove(fwElement);
}
Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
Outdated
Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
Comment on lines +35 to +49

handler.PlatformView.Content = platformView;
}
else
{
handler.PlatformView.Content = null;
}

}
Expand Down
20 changes: 15 additions & 5 deletions src/Core/src/Handlers/ContentView/ContentViewHandler.Windows.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using Microsoft.Maui.Graphics;
using Microsoft.UI.Xaml;

namespace Microsoft.Maui.Handlers
{
Expand All @@ -21,13 +22,22 @@ static void UpdateContent(IContentViewHandler handler)
_ = handler.VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class.");
_ = handler.MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class.");

handler.PlatformView.CachedChildren.Clear();

if (handler.VirtualView.PresentedContent is IView view)
{
// Detach the old handler if it exists (prevents WinUI COM exception on reuse)
view.Handler?.DisconnectHandler();
handler.PlatformView.CachedChildren.Add(view.ToPlatform(handler.MauiContext));
var platformView = view.ToPlatform(handler.MauiContext);

// Detach from existing parent — mirrors Android RemoveFromParent / iOS RemoveFromSuperview
if (platformView is FrameworkElement fwElement &&
fwElement.Parent is MauiPanel existingParent)
{
existingParent.CachedChildren.Remove(fwElement);
}
Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
Outdated
Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
Comment on lines +34 to +48

handler.PlatformView.Content = platformView;
}
else
{
handler.PlatformView.Content = null;
}
}

Expand Down
Loading